diff --git a/spring-boot-modules/pom.xml b/spring-boot-modules/pom.xml
index 655bcb67cf26..9aaaf8eac83f 100644
--- a/spring-boot-modules/pom.xml
+++ b/spring-boot-modules/pom.xml
@@ -116,6 +116,7 @@
spring-boot-3-4
spring-boot-4
spring-boot-resilience4j
+ spring-boot-retries
spring-boot-properties
spring-boot-properties-2
spring-boot-properties-3
diff --git a/spring-boot-modules/spring-boot-retries/pom.xml b/spring-boot-modules/spring-boot-retries/pom.xml
new file mode 100644
index 000000000000..4fe8e29ceb2d
--- /dev/null
+++ b/spring-boot-modules/spring-boot-retries/pom.xml
@@ -0,0 +1,64 @@
+
+
+ 4.0.0
+ com.baeldung.spring-boot-retries
+ spring-boot-retries
+ 1.0.0-SNAPSHOT
+ spring-boot-retries
+
+
+ com.baeldung.spring-boot-modules
+ spring-boot-modules
+ 1.0.0-SNAPSHOT
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.apache.httpcomponents.client5
+ httpclient5
+ 5.3.1
+
+
+ org.apache.httpcomponents.core5
+ httpcore5
+ 5.2.1
+
+
+
+ org.springframework.retry
+ spring-retry
+
+
+ org.springframework
+ spring-aspects
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/RetrylogicApplication.java b/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/RetrylogicApplication.java
new file mode 100644
index 000000000000..2b642743c6f4
--- /dev/null
+++ b/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/RetrylogicApplication.java
@@ -0,0 +1,14 @@
+package com.baeldung.retries;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class RetrylogicApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(RetrylogicApplication.class, args);
+ }
+
+}
+
diff --git a/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/config/RestTemplateConfig.java b/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/config/RestTemplateConfig.java
new file mode 100644
index 000000000000..cfada2b85e7e
--- /dev/null
+++ b/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/config/RestTemplateConfig.java
@@ -0,0 +1,23 @@
+package com.baeldung.retries.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.retry.annotation.EnableRetry;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+@EnableRetry
+public class RestTemplateConfig {
+
+ @Bean
+ public RestTemplate restTemplate() {
+ HttpComponentsClientHttpRequestFactory factory =
+ new HttpComponentsClientHttpRequestFactory();
+ factory.setConnectTimeout(5000);
+ factory.setConnectionRequestTimeout(5000);
+
+ return new RestTemplate(factory);
+ }
+}
+
diff --git a/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/config/RetryTemplateConfig.java b/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/config/RetryTemplateConfig.java
new file mode 100644
index 000000000000..2cb8c8c888c4
--- /dev/null
+++ b/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/config/RetryTemplateConfig.java
@@ -0,0 +1,28 @@
+package com.baeldung.retries.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.retry.backoff.FixedBackOffPolicy;
+import org.springframework.retry.policy.SimpleRetryPolicy;
+import org.springframework.retry.support.RetryTemplate;
+
+@Configuration
+public class RetryTemplateConfig {
+
+ @Bean
+ public RetryTemplate retryTemplate() {
+ RetryTemplate retryTemplate = new RetryTemplate();
+
+ FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
+ backOffPolicy.setBackOffPeriod(2000);
+
+ SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
+ retryPolicy.setMaxAttempts(3);
+
+ retryTemplate.setBackOffPolicy(backOffPolicy);
+ retryTemplate.setRetryPolicy(retryPolicy);
+
+ return retryTemplate;
+ }
+}
+
diff --git a/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/controller/RetryController.java b/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/controller/RetryController.java
new file mode 100644
index 000000000000..8e18bd5e54b6
--- /dev/null
+++ b/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/controller/RetryController.java
@@ -0,0 +1,48 @@
+package com.baeldung.retries.controller;
+
+import com.baeldung.retries.service.ExponentialBackoffRetryService;
+import com.baeldung.retries.service.RestTemplateRetryService;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api")
+public class RetryController {
+
+ private final RestTemplateRetryService retryService;
+ private final ExponentialBackoffRetryService exponentialService;
+
+ public RetryController(RestTemplateRetryService retryService,
+ ExponentialBackoffRetryService exponentialService) {
+ this.retryService = retryService;
+ this.exponentialService = exponentialService;
+ }
+
+ @GetMapping("/fetch-with-retry")
+ public ResponseEntity fetchWithRetry(@RequestParam String url) {
+ try {
+ String result = retryService.makeRequestWithRetry(url);
+ return ResponseEntity.ok(result);
+ } catch (RuntimeException e) {
+ return ResponseEntity.status(503)
+ .body("Service unavailable after retries: " + e.getMessage());
+ }
+ }
+
+ @GetMapping("/fetch-with-exponential-backoff")
+ public ResponseEntity fetchWithExponentialBackoff(
+ @RequestParam String url) {
+ try {
+ String result = exponentialService
+ .makeRequestWithExponentialBackoff(url);
+ return ResponseEntity.ok(result);
+ } catch (RuntimeException e) {
+ return ResponseEntity.status(503)
+ .body("Service unavailable after retries: " + e.getMessage());
+ }
+ }
+}
+
diff --git a/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/service/ExponentialBackoffRetryService.java b/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/service/ExponentialBackoffRetryService.java
new file mode 100644
index 000000000000..7f74d5e368e6
--- /dev/null
+++ b/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/service/ExponentialBackoffRetryService.java
@@ -0,0 +1,49 @@
+package com.baeldung.retries.service;
+
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.ResourceAccessException;
+import org.springframework.web.client.RestTemplate;
+
+@Service
+public class ExponentialBackoffRetryService {
+
+ private final RestTemplate restTemplate;
+ private int maxRetries = 5;
+ private long initialDelay = 1000;
+
+ public ExponentialBackoffRetryService(RestTemplate restTemplate) {
+ this.restTemplate = restTemplate;
+ }
+
+ public String makeRequestWithExponentialBackoff(String url) {
+ int attempt = 0;
+ while (attempt < maxRetries) {
+ try {
+ return restTemplate.getForObject(url, String.class);
+ } catch (ResourceAccessException e) {
+ attempt++;
+ if (attempt >= maxRetries) {
+ throw new RuntimeException(
+ "Failed after " + maxRetries + " attempts", e);
+ }
+ long delay = initialDelay * (long) Math.pow(2, attempt - 1);
+ try {
+ Thread.sleep(delay);
+ } catch (InterruptedException ie) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException("Retry interrupted", ie);
+ }
+ }
+ }
+ throw new RuntimeException("Unexpected error in retry logic");
+ }
+
+ public void setMaxRetries(int maxRetries) {
+ this.maxRetries = maxRetries;
+ }
+
+ public void setInitialDelay(long initialDelay) {
+ this.initialDelay = initialDelay;
+ }
+}
+
diff --git a/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/service/RestClientService.java b/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/service/RestClientService.java
new file mode 100644
index 000000000000..699e3353335c
--- /dev/null
+++ b/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/service/RestClientService.java
@@ -0,0 +1,33 @@
+package com.baeldung.retries.service;
+
+import org.springframework.retry.annotation.Backoff;
+import org.springframework.retry.annotation.Retryable;
+import org.springframework.retry.annotation.Recover;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.ResourceAccessException;
+import org.springframework.web.client.RestTemplate;
+
+
+@Service
+public class RestClientService {
+
+ private final RestTemplate restTemplate;
+
+ public RestClientService(RestTemplate restTemplate) {
+ this.restTemplate = restTemplate;
+ }
+
+ @Retryable(
+ retryFor = {ResourceAccessException.class},
+ maxAttempts = 3,
+ backoff = @Backoff(delay = 2000))
+ public String fetchData(String url) {
+ return restTemplate.getForObject(url, String.class);
+ }
+
+ @Recover
+ public String recover(ResourceAccessException e, String url) {
+ return "Fallback response after all retries failed for: " + url;
+ }
+}
+
diff --git a/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/service/RestTemplateRetryService.java b/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/service/RestTemplateRetryService.java
new file mode 100644
index 000000000000..aa3ee4421cc3
--- /dev/null
+++ b/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/service/RestTemplateRetryService.java
@@ -0,0 +1,48 @@
+package com.baeldung.retries.service;
+
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.ResourceAccessException;
+import org.springframework.web.client.RestTemplate;
+
+@Service
+public class RestTemplateRetryService {
+
+ private final RestTemplate restTemplate;
+ private int maxRetries = 3;
+ private long retryDelay = 2000;
+
+ public RestTemplateRetryService(RestTemplate restTemplate) {
+ this.restTemplate = restTemplate;
+ }
+
+ public String makeRequestWithRetry(String url) {
+ int attempt = 0;
+ while (attempt < maxRetries) {
+ try {
+ return restTemplate.getForObject(url, String.class);
+ } catch (ResourceAccessException e) {
+ attempt++;
+ if (attempt >= maxRetries) {
+ throw new RuntimeException(
+ "Failed after " + maxRetries + " attempts", e);
+ }
+ try {
+ Thread.sleep(retryDelay);
+ } catch (InterruptedException ie) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException("Retry interrupted", ie);
+ }
+ }
+ }
+ throw new RuntimeException("Unexpected error in retry logic");
+ }
+
+ public void setMaxRetries(int maxRetries) {
+ this.maxRetries = maxRetries;
+ }
+
+ public void setRetryDelay(long retryDelay) {
+ this.retryDelay = retryDelay;
+ }
+}
+
diff --git a/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/service/RetryTemplateService.java b/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/service/RetryTemplateService.java
new file mode 100644
index 000000000000..f12d82fdff17
--- /dev/null
+++ b/spring-boot-modules/spring-boot-retries/src/main/java/com/baeldung/retries/service/RetryTemplateService.java
@@ -0,0 +1,26 @@
+package com.baeldung.retries.service;
+
+import org.springframework.retry.support.RetryTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+@Service
+public class RetryTemplateService {
+
+ private final RestTemplate restTemplate;
+ private final RetryTemplate retryTemplate;
+
+ public RetryTemplateService(RestTemplate restTemplate, RetryTemplate retryTemplate) {
+ this.restTemplate = restTemplate;
+ this.retryTemplate = retryTemplate;
+ }
+
+ public String fetchDataWithRetryTemplate(String url) {
+ return retryTemplate.execute(context -> {
+ return restTemplate.getForObject(url, String.class);
+ }, context -> {
+ return "Fallback response";
+ });
+ }
+}
+
diff --git a/spring-boot-modules/spring-boot-retries/src/main/resources/application.properties b/spring-boot-modules/spring-boot-retries/src/main/resources/application.properties
new file mode 100644
index 000000000000..a1518926c186
--- /dev/null
+++ b/spring-boot-modules/spring-boot-retries/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+spring.application.name=retrylogic
+server.port=8080
+logging.level.org.springframework.web.client=DEBUG
diff --git a/spring-boot-modules/spring-boot-retries/src/test/java/com/baeldung/retries/retrylogic/RetrylogicApplicationTests.java b/spring-boot-modules/spring-boot-retries/src/test/java/com/baeldung/retries/retrylogic/RetrylogicApplicationTests.java
new file mode 100644
index 000000000000..ee84b7062ab9
--- /dev/null
+++ b/spring-boot-modules/spring-boot-retries/src/test/java/com/baeldung/retries/retrylogic/RetrylogicApplicationTests.java
@@ -0,0 +1,14 @@
+package com.baeldung.retries.retrylogic;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class RetrylogicApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
+
diff --git a/spring-boot-modules/spring-boot-retries/src/test/java/com/baeldung/retries/service/ExponentialBackoffRetryServiceTest.java b/spring-boot-modules/spring-boot-retries/src/test/java/com/baeldung/retries/service/ExponentialBackoffRetryServiceTest.java
new file mode 100644
index 000000000000..c4c7cbd90ac1
--- /dev/null
+++ b/spring-boot-modules/spring-boot-retries/src/test/java/com/baeldung/retries/service/ExponentialBackoffRetryServiceTest.java
@@ -0,0 +1,32 @@
+package com.baeldung.retries.service;
+
+import com.baeldung.retries.RetrylogicApplication;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@SpringBootTest(classes = RetrylogicApplication.class)
+class ExponentialBackoffRetryServiceTest {
+
+ @Autowired
+ private ExponentialBackoffRetryService service;
+
+ @Test
+ void whenHostOffline_thenRetriesWithExponentialBackoff() {
+ service.setMaxRetries(4);
+ service.setInitialDelay(500);
+
+ String offlineUrl = "http://localhost:9999/api/data";
+ long startTime = System.currentTimeMillis();
+
+ assertThrows(RuntimeException.class, () -> {
+ service.makeRequestWithExponentialBackoff(offlineUrl);
+ });
+
+ long duration = System.currentTimeMillis() - startTime;
+ assertTrue(duration >= 3500);
+ }
+}
+
diff --git a/spring-boot-modules/spring-boot-retries/src/test/java/com/baeldung/retries/service/RestClientServiceTest.java b/spring-boot-modules/spring-boot-retries/src/test/java/com/baeldung/retries/service/RestClientServiceTest.java
new file mode 100644
index 000000000000..709b68d6bcfd
--- /dev/null
+++ b/spring-boot-modules/spring-boot-retries/src/test/java/com/baeldung/retries/service/RestClientServiceTest.java
@@ -0,0 +1,26 @@
+package com.baeldung.retries.service;
+
+import com.baeldung.retries.RetrylogicApplication;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@SpringBootTest(classes = RetrylogicApplication.class)
+class RestClientServiceTest {
+
+ @Autowired
+ private RestClientService restClientService;
+
+ @Test
+ void whenHostOffline_thenRetriesAndRecovers() {
+ String offlineUrl = "http://localhost:9999/api/data";
+
+ String result = restClientService.fetchData(offlineUrl);
+
+ assertTrue(result.contains("Fallback response"));
+ assertTrue(result.contains(offlineUrl));
+ }
+}
+
diff --git a/spring-boot-modules/spring-boot-retries/src/test/java/com/baeldung/retries/service/RestTemplateRetryServiceTest.java b/spring-boot-modules/spring-boot-retries/src/test/java/com/baeldung/retries/service/RestTemplateRetryServiceTest.java
new file mode 100644
index 000000000000..d1a08a01fe79
--- /dev/null
+++ b/spring-boot-modules/spring-boot-retries/src/test/java/com/baeldung/retries/service/RestTemplateRetryServiceTest.java
@@ -0,0 +1,32 @@
+package com.baeldung.retries.service;
+
+
+import com.baeldung.retries.RetrylogicApplication;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@SpringBootTest(classes = RetrylogicApplication.class)
+class RestTemplateRetryServiceTest {
+
+ @Autowired
+ private RestTemplateRetryService service;
+
+ @Test
+ void whenHostOffline_thenRetriesAndFails() {
+ String offlineUrl = "http://localhost:9999/api/data";
+
+ long startTime = System.currentTimeMillis();
+
+ assertThrows(RuntimeException.class, () -> {
+ service.makeRequestWithRetry(offlineUrl);
+ });
+
+ long duration = System.currentTimeMillis() - startTime;
+ assertTrue(duration >= 4000);
+ }
+
+}
+
diff --git a/spring-boot-modules/spring-boot-retries/src/test/java/com/baeldung/retries/service/RetryTemplateServiceTest.java b/spring-boot-modules/spring-boot-retries/src/test/java/com/baeldung/retries/service/RetryTemplateServiceTest.java
new file mode 100644
index 000000000000..30b8ec3ebcf2
--- /dev/null
+++ b/spring-boot-modules/spring-boot-retries/src/test/java/com/baeldung/retries/service/RetryTemplateServiceTest.java
@@ -0,0 +1,35 @@
+package com.baeldung.retries.service;
+
+import com.baeldung.retries.RetrylogicApplication;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ContextConfiguration;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@SpringBootTest
+@ContextConfiguration(classes = {
+ RetryTemplateService.class,
+ RetrylogicApplication .class
+})
+class RetryTemplateServiceTest {
+
+ @Autowired
+ private RetryTemplateService retryTemplateService;
+
+ @Test
+ void whenHostOffline_thenReturnsFallback() {
+ String offlineUrl = "http://localhost:9999/api/data";
+
+ long startTime = System.currentTimeMillis();
+ String result = retryTemplateService
+ .fetchDataWithRetryTemplate(offlineUrl);
+ long duration = System.currentTimeMillis() - startTime;
+
+ assertEquals("Fallback response", result);
+ assertTrue(duration >= 4000);
+ }
+
+}
+
diff --git a/spring-boot-modules/spring-boot-retries/src/test/resources/application.properties b/spring-boot-modules/spring-boot-retries/src/test/resources/application.properties
new file mode 100644
index 000000000000..907771133c8c
--- /dev/null
+++ b/spring-boot-modules/spring-boot-retries/src/test/resources/application.properties
@@ -0,0 +1,3 @@
+server.port=8080
+logging.level.org.springframework.web.client=DEBUG
+