diff --git a/spring-cloud/pom.xml b/spring-cloud/pom.xml
index 3de527c33b..6fddb1693f 100644
--- a/spring-cloud/pom.xml
+++ b/spring-cloud/pom.xml
@@ -40,6 +40,7 @@
spring-cloud-task
spring-cloud-zuul
spring-cloud-zuul-fallback
+ spring-cloud-ribbon-retry
diff --git a/spring-cloud/spring-cloud-ribbon-retry/pom.xml b/spring-cloud/spring-cloud-ribbon-retry/pom.xml
new file mode 100644
index 0000000000..5318ea6913
--- /dev/null
+++ b/spring-cloud/spring-cloud-ribbon-retry/pom.xml
@@ -0,0 +1,40 @@
+
+
+ 4.0.0
+ com.baeldung.spring.cloud
+ spring-cloud-ribbon-retry
+ 0.0.1-SNAPSHOT
+ spring-cloud-ribbon-retry
+ pom
+
+
+ com.baeldung
+ parent-boot-2
+ 0.0.1-SNAPSHOT
+ ../../parent-boot-2
+
+
+
+ ribbon-client-service
+ ribbon-weather-service
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-parent
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+ Hoxton.SR3
+
+
+
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/pom.xml b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/pom.xml
new file mode 100644
index 0000000000..ad47eb6c84
--- /dev/null
+++ b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/pom.xml
@@ -0,0 +1,39 @@
+
+
+ 4.0.0
+ ribbon-client-service
+
+
+ com.baeldung.spring.cloud
+ spring-cloud-ribbon-retry
+ 0.0.1-SNAPSHOT
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-ribbon
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.retry
+ spring-retry
+
+
+ com.baeldung.spring.cloud
+ ribbon-weather-service
+ 0.0.1-SNAPSHOT
+ test
+
+
+
+
+ Hoxton.SR3
+
+
+
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/RibbonClientApp.java b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/RibbonClientApp.java
new file mode 100644
index 0000000000..e06d4a93a1
--- /dev/null
+++ b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/RibbonClientApp.java
@@ -0,0 +1,12 @@
+package com.baeldung.spring.cloud.ribbon.retry;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class RibbonClientApp {
+
+ public static void main(String[] args) {
+ SpringApplication.run(RibbonClientApp.class, args);
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/backoff/ExponentialBackoffRetryFactory.java b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/backoff/ExponentialBackoffRetryFactory.java
new file mode 100644
index 0000000000..c70ee71b7d
--- /dev/null
+++ b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/backoff/ExponentialBackoffRetryFactory.java
@@ -0,0 +1,26 @@
+package com.baeldung.spring.cloud.ribbon.retry.backoff;
+
+import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryFactory;
+import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
+import org.springframework.context.annotation.Profile;
+import org.springframework.retry.backoff.BackOffPolicy;
+import org.springframework.retry.backoff.ExponentialBackOffPolicy;
+import org.springframework.stereotype.Component;
+
+@Component
+@Profile("exponential-backoff")
+class ExponentialBackoffRetryFactory extends RibbonLoadBalancedRetryFactory {
+
+ public ExponentialBackoffRetryFactory(SpringClientFactory clientFactory) {
+ super(clientFactory);
+ }
+
+ @Override
+ public BackOffPolicy createBackOffPolicy(String service) {
+ ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
+ exponentialBackOffPolicy.setInitialInterval(1000);
+ exponentialBackOffPolicy.setMultiplier(2);
+ exponentialBackOffPolicy.setMaxInterval(10000);
+ return exponentialBackOffPolicy;
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/backoff/ExponentialRandomBackoffRetryFactory.java b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/backoff/ExponentialRandomBackoffRetryFactory.java
new file mode 100644
index 0000000000..c1fad4d1a0
--- /dev/null
+++ b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/backoff/ExponentialRandomBackoffRetryFactory.java
@@ -0,0 +1,26 @@
+package com.baeldung.spring.cloud.ribbon.retry.backoff;
+
+import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryFactory;
+import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
+import org.springframework.context.annotation.Profile;
+import org.springframework.retry.backoff.BackOffPolicy;
+import org.springframework.retry.backoff.ExponentialRandomBackOffPolicy;
+import org.springframework.stereotype.Component;
+
+@Component
+@Profile("exponential-random-backoff")
+class ExponentialRandomBackoffRetryFactory extends RibbonLoadBalancedRetryFactory {
+
+ public ExponentialRandomBackoffRetryFactory(SpringClientFactory clientFactory) {
+ super(clientFactory);
+ }
+
+ @Override
+ public BackOffPolicy createBackOffPolicy(String service) {
+ ExponentialRandomBackOffPolicy exponentialRandomBackOffPolicy = new ExponentialRandomBackOffPolicy();
+ exponentialRandomBackOffPolicy.setInitialInterval(1000);
+ exponentialRandomBackOffPolicy.setMultiplier(2);
+ exponentialRandomBackOffPolicy.setMaxInterval(10000);
+ return exponentialRandomBackOffPolicy;
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/backoff/FixedBackoffRetryFactory.java b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/backoff/FixedBackoffRetryFactory.java
new file mode 100644
index 0000000000..6dab5d15b4
--- /dev/null
+++ b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/backoff/FixedBackoffRetryFactory.java
@@ -0,0 +1,24 @@
+package com.baeldung.spring.cloud.ribbon.retry.backoff;
+
+import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryFactory;
+import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
+import org.springframework.context.annotation.Profile;
+import org.springframework.retry.backoff.BackOffPolicy;
+import org.springframework.retry.backoff.FixedBackOffPolicy;
+import org.springframework.stereotype.Component;
+
+@Component
+@Profile("fixed-backoff")
+class FixedBackoffRetryFactory extends RibbonLoadBalancedRetryFactory {
+
+ public FixedBackoffRetryFactory(SpringClientFactory clientFactory) {
+ super(clientFactory);
+ }
+
+ @Override
+ public BackOffPolicy createBackOffPolicy(String service) {
+ FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
+ fixedBackOffPolicy.setBackOffPeriod(2000);
+ return fixedBackOffPolicy;
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/config/RibbonConfiguration.java b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/config/RibbonConfiguration.java
new file mode 100644
index 0000000000..c493b4dbe2
--- /dev/null
+++ b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/config/RibbonConfiguration.java
@@ -0,0 +1,20 @@
+package com.baeldung.spring.cloud.ribbon.retry.config;
+
+import com.netflix.loadbalancer.IPing;
+import com.netflix.loadbalancer.IRule;
+import com.netflix.loadbalancer.PingUrl;
+import com.netflix.loadbalancer.WeightedResponseTimeRule;
+import org.springframework.context.annotation.Bean;
+
+public class RibbonConfiguration {
+
+ @Bean
+ public IPing ribbonPing() {
+ return new PingUrl();
+ }
+
+ @Bean
+ public IRule ribbonRule() {
+ return new WeightedResponseTimeRule();
+ }
+}
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/config/WeatherClientRibbonConfiguration.java b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/config/WeatherClientRibbonConfiguration.java
new file mode 100644
index 0000000000..88955db025
--- /dev/null
+++ b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/config/WeatherClientRibbonConfiguration.java
@@ -0,0 +1,19 @@
+package com.baeldung.spring.cloud.ribbon.retry.config;
+
+import org.springframework.cloud.client.loadbalancer.LoadBalanced;
+import org.springframework.cloud.netflix.ribbon.RibbonClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+@RibbonClient(name = "weather-service", configuration = RibbonConfiguration.class)
+public class WeatherClientRibbonConfiguration {
+
+ @LoadBalanced
+ @Bean
+ RestTemplate getRestTemplate() {
+ return new RestTemplate();
+ }
+
+}
diff --git a/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/controller/RibbonClientController.java b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/controller/RibbonClientController.java
new file mode 100644
index 0000000000..ebe5b58386
--- /dev/null
+++ b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/controller/RibbonClientController.java
@@ -0,0 +1,21 @@
+package com.baeldung.spring.cloud.ribbon.retry.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestTemplate;
+
+@RestController
+public class RibbonClientController {
+
+ private static final String WEATHER_SERVICE = "weather-service";
+
+ @Autowired
+ private RestTemplate restTemplate;
+
+ @GetMapping("/client/weather")
+ public String weather() {
+ String result = restTemplate.getForObject("http://" + WEATHER_SERVICE + "/weather", String.class);
+ return "Weather Service Response: " + result;
+ }
+}
diff --git a/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/resources/application.yml b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/resources/application.yml
new file mode 100644
index 0000000000..3199f38dce
--- /dev/null
+++ b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/main/resources/application.yml
@@ -0,0 +1,17 @@
+spring:
+ profiles:
+ # fixed-backoff, exponential-backoff, exponential-random-backoff
+ active: fixed-backoff
+ application:
+ name: ribbon-client
+
+weather-service:
+ ribbon:
+ eureka:
+ enabled: false
+ listOfServers: http://localhost:8081, http://localhost:8082
+ ServerListRefreshInterval: 5000
+ MaxAutoRetries: 3
+ MaxAutoRetriesNextServer: 1
+ OkToRetryOnAllOperations: true
+ retryableStatusCodes: 503, 408
diff --git a/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/test/java/com/baeldung/spring/cloud/ribbon/retry/RibbonRetryFailureIntegrationTest.java b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/test/java/com/baeldung/spring/cloud/ribbon/retry/RibbonRetryFailureIntegrationTest.java
new file mode 100644
index 0000000000..0e72bdbb86
--- /dev/null
+++ b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/test/java/com/baeldung/spring/cloud/ribbon/retry/RibbonRetryFailureIntegrationTest.java
@@ -0,0 +1,50 @@
+package com.baeldung.spring.cloud.ribbon.retry;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.http.ResponseEntity;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = RibbonClientApp.class)
+public class RibbonRetryFailureIntegrationTest {
+
+ private static ConfigurableApplicationContext weatherServiceInstance1;
+ private static ConfigurableApplicationContext weatherServiceInstance2;
+
+ @LocalServerPort
+ private int port;
+ private TestRestTemplate restTemplate = new TestRestTemplate();
+
+ @BeforeAll
+ public static void setup() {
+ weatherServiceInstance1 = startApp(8081);
+ weatherServiceInstance2 = startApp(8082);
+ }
+
+ @AfterAll
+ public static void cleanup() {
+ weatherServiceInstance1.close();
+ weatherServiceInstance2.close();
+ }
+
+ private static ConfigurableApplicationContext startApp(int port) {
+ return SpringApplication.run(RibbonWeatherServiceApp.class, "--server.port=" + port, "--successful.call.divisor=6");
+ }
+
+ @Test
+ public void whenRibbonClientIsCalledAndServiceUnavailable_thenFailure() {
+ String url = "http://localhost:" + port + "/client/weather";
+
+ ResponseEntity response = restTemplate.getForEntity(url, String.class);
+
+ assertTrue(response.getStatusCode().is5xxServerError());
+ }
+
+}
diff --git a/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/test/java/com/baeldung/spring/cloud/ribbon/retry/RibbonRetrySuccessIntegrationTest.java b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/test/java/com/baeldung/spring/cloud/ribbon/retry/RibbonRetrySuccessIntegrationTest.java
new file mode 100644
index 0000000000..2055159117
--- /dev/null
+++ b/spring-cloud/spring-cloud-ribbon-retry/ribbon-client-service/src/test/java/com/baeldung/spring/cloud/ribbon/retry/RibbonRetrySuccessIntegrationTest.java
@@ -0,0 +1,52 @@
+package com.baeldung.spring.cloud.ribbon.retry;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.http.ResponseEntity;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = RibbonClientApp.class)
+public class RibbonRetrySuccessIntegrationTest {
+
+ private static ConfigurableApplicationContext weatherServiceInstance1;
+ private static ConfigurableApplicationContext weatherServiceInstance2;
+
+ @LocalServerPort
+ private int port;
+ private TestRestTemplate restTemplate = new TestRestTemplate();
+
+ @BeforeAll
+ public static void setup() {
+ weatherServiceInstance1 = startApp(8081);
+ weatherServiceInstance2 = startApp(8082);
+ }
+
+ private static ConfigurableApplicationContext startApp(int port) {
+ return SpringApplication.run(RibbonWeatherServiceApp.class, "--server.port=" + port, "--successful.call.divisor=3");
+ }
+
+ @AfterAll
+ public static void cleanup() {
+ weatherServiceInstance1.close();
+ weatherServiceInstance2.close();
+ }
+
+ @Test
+ public void whenRibbonClientIsCalledAndServiceAvailable_thenSuccess() {
+ String url = "http://localhost:" + port + "/client/weather";
+
+ ResponseEntity response = restTemplate.getForEntity(url, String.class);
+
+ assertTrue(response.getStatusCode().is2xxSuccessful());
+ assertEquals(response.getBody(), "Weather Service Response: Today's a sunny day");
+ }
+
+}
diff --git a/spring-cloud/spring-cloud-ribbon-retry/ribbon-weather-service/pom.xml b/spring-cloud/spring-cloud-ribbon-retry/ribbon-weather-service/pom.xml
new file mode 100644
index 0000000000..f091341025
--- /dev/null
+++ b/spring-cloud/spring-cloud-ribbon-retry/ribbon-weather-service/pom.xml
@@ -0,0 +1,21 @@
+
+
+ 4.0.0
+ ribbon-weather-service
+
+
+ com.baeldung.spring.cloud
+ spring-cloud-ribbon-retry
+ 0.0.1-SNAPSHOT
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
\ No newline at end of file
diff --git a/spring-cloud/spring-cloud-ribbon-retry/ribbon-weather-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/RibbonWeatherServiceApp.java b/spring-cloud/spring-cloud-ribbon-retry/ribbon-weather-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/RibbonWeatherServiceApp.java
new file mode 100644
index 0000000000..ceeacbd426
--- /dev/null
+++ b/spring-cloud/spring-cloud-ribbon-retry/ribbon-weather-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/RibbonWeatherServiceApp.java
@@ -0,0 +1,12 @@
+package com.baeldung.spring.cloud.ribbon.retry;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class RibbonWeatherServiceApp {
+
+ public static void main(String[] args) {
+ SpringApplication.run(RibbonWeatherServiceApp.class, args);
+ }
+}
diff --git a/spring-cloud/spring-cloud-ribbon-retry/ribbon-weather-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/WeatherController.java b/spring-cloud/spring-cloud-ribbon-retry/ribbon-weather-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/WeatherController.java
new file mode 100644
index 0000000000..ec0b94e505
--- /dev/null
+++ b/spring-cloud/spring-cloud-ribbon-retry/ribbon-weather-service/src/main/java/com/baeldung/spring/cloud/ribbon/retry/WeatherController.java
@@ -0,0 +1,39 @@
+package com.baeldung.spring.cloud.ribbon.retry;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class WeatherController {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(WeatherController.class);
+
+ private int nrOfCalls = 0;
+
+ @Value("${successful.call.divisor}")
+ private int divisor;
+
+ @GetMapping("/")
+ public String health() {
+ return "I am Ok";
+ }
+
+ @GetMapping("/weather")
+ public ResponseEntity weather() {
+ LOGGER.info("Providing today's weather information");
+ if (isServiceUnavailable()) {
+ return new ResponseEntity<>(HttpStatus.SERVICE_UNAVAILABLE);
+ }
+ LOGGER.info("Today's a sunny day");
+ return new ResponseEntity<>("Today's a sunny day", HttpStatus.OK);
+ }
+
+ private boolean isServiceUnavailable() {
+ return ++nrOfCalls % divisor != 0;
+ }
+}
diff --git a/spring-cloud/spring-cloud-ribbon-retry/ribbon-weather-service/src/main/resources/application.properties b/spring-cloud/spring-cloud-ribbon-retry/ribbon-weather-service/src/main/resources/application.properties
new file mode 100644
index 0000000000..ea25e8f2da
--- /dev/null
+++ b/spring-cloud/spring-cloud-ribbon-retry/ribbon-weather-service/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+spring.application.name=weather-service
+successful.call.divisor=3