73

I'm writing some reconnect logic to periodically attempt to establish a connection to a remote endpoint which went down. Essentially, the code looks like this:

public void establishConnection() {
    try {
        this.connection = newConnection();
    } catch (IOException e) {
        // connection failed, try again.
        try { Thread.sleep(1000); } catch (InterruptedException e) {};

        establishConnection();
    }
}

I've solved this general problem with code similar to the above on many occasions, but I feel largely unsatisfied with the result. Is there a design pattern designed for dealing with this issue?

Naftuli Kay
  • 87,710
  • 93
  • 269
  • 411
  • 1
    like try three times then raise the flag pattern? :) – Nishant Jul 27 '12 at 17:21
  • in this case I'd be tempted to use a loop rather than recursion - otherwise if it sits there for ages you're going to end up with a huge stack. It'd also make it easier to break out after a certain number of retries (if you use a for loop for instance you have this mechanism built in.) – Michael Berry Jul 27 '12 at 17:24
  • can you catch the "disconnected" event? – Shark Jul 27 '12 at 17:46
  • 1
    there are some good answers here. one thing to note (not mentioned here), is that it is generally a good idea to implement some sort of backoff strategy (wait longer between successive retries). this will avoid pounding on a box which is not responding (possibly due to load issues). – jtahlborn Jul 27 '12 at 18:30
  • @jtahlborn: that's one of the wait strategies available in my solution :-) – JB Nizet Jul 28 '12 at 09:44

12 Answers12

31

Shameless plug: I have implemented some classes to allow retrying operations. The library is not made available yet, but you may fork it on github. And a fork exists.

It allows building a Retryer with various flexible strategies. For example:

Retryer retryer = 
    RetryerBuilder.newBuilder()
                  .withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECOND))
                  .withStopStrategy(StopStrategies.stopAfterAttempt(3))
                  .retryIfExceptionOfType(IOException.class)
                  .build();

And you can then execute a callable (or several ones) with the Retryer:

retryer.call(new Callable<Void>() {
    public Void call() throws IOException {
        connection = newConnection();
        return null;
    }
}
juherr
  • 5,640
  • 1
  • 21
  • 63
JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • I've built something similar to this for work, though using a decorator pattern, rather than a builder pattern. Either one gets the job done, and is a very elegant way to do retries. It allows you to write a simple Callable implementation and not have to code the retry logic yourself. – Matt Jul 27 '12 at 17:51
  • Actually, the builder is just used to configure an immutabe retryer. The retryer can be used to wrap a Callable into another one. So it also uses the decorator pattern. – JB Nizet Jul 27 '12 at 18:13
  • @JBNizet thanks for code snippet but it gives error on `withStopStrategy(StopStrategies.stopAfterAttempt(3))` line the error is `The method withStopStrategy(StopStrategy) is undefined for the type WaitStrategy`. here the whole line which I am using `Retryer retryer = RetryerBuilder.newBuilder().withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS).withStopStrategy(StopStrategies.stopAfterAttempt(5)).retryIfExceptionOfType(IOException.class).build());` – Bhargav Modi Mar 04 '15 at 08:09
  • 2
    You (and I) forgot to close a parenthesis: `withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))` <-- two perentheses here – JB Nizet Mar 04 '15 at 08:14
  • 1
    I had great luck with a project forked from the above author's code: https://github.com/rholder/guava-retrying – jstricker Mar 31 '15 at 02:22
  • @JBNizet I am also using guava retry in one of my project and I am confuse regarding the usage of thread safety so I have a question [here](http://stackoverflow.com/questions/41925494/how-to-keep-retrying-block-machine-every-x-interval-until-url-is-executed-succes) and I wanted to see if you can help me out? – john Feb 03 '17 at 19:54
29

You could try the Idempotent Retry Pattern.

enter image description here

GalacticJello
  • 11,235
  • 2
  • 25
  • 35
17

I really like this Java 8 code from this blog and you don't need any extra library on your classpath.

You only need to pass a function to the retry class.

@Slf4j
public class RetryCommand<T> {

    private int maxRetries;

    RetryCommand(int maxRetries)
    {
        this.maxRetries = maxRetries;
    }

    // Takes a function and executes it, if fails, passes the function to the retry command
    public T run(Supplier<T> function) {
        try {
            return function.get();
        } catch (Exception e) {
            log.error("FAILED - Command failed, will be retried " + maxRetries + " times.");
            return retry(function);
        }
    }

    private T retry(Supplier<T> function) throws RuntimeException {

        int retryCounter = 0;
        while (retryCounter < maxRetries) {
            try {
                return function.get();
            } catch (Exception ex) {
                retryCounter++;
                log.error("FAILED - Command failed on retry " + retryCounter + " of " + maxRetries, ex);
                if (retryCounter >= maxRetries) {
                    log.error("Max retries exceeded.");
                    break;
                }
            }
        }
        throw new RuntimeException("Command failed on all of " + maxRetries + " retries");
    }
}

And to use it:

new RetryCommand<>(5).run(() -> client.getThatThing(id));
Dherik
  • 17,757
  • 11
  • 115
  • 164
  • 1
    I love that approach, but it doesn't work with checked exceptions. – tristobal Mar 16 '21 at 22:33
  • retries to external systems without linear/expo delay + random jitter can overload the target service, specially if it is experiencing resource starvation due to excessive load. – Ashwin Prabhu Nov 14 '22 at 08:50
  • 1
    @tristobal, what do you mean it doesn't work with checked exceptions? Seems like the author is catching all exceptions there and retries them all. Or is your concern that it doesn't propagate checked exceptions downstream to caller? – Zhenya Aug 17 '23 at 13:59
13

Using Failsafe (author here):

RetryPolicy retryPolicy = new RetryPolicy()
  .retryOn(IOException.class)
  .withMaxRetries(5)
  .withDelay(1, TimeUnit.SECONDS);

Failsafe.with(retryPolicy).run(() -> newConnection());

No annotations, no magic, doesn't need to be a Spring app, etc. Just straightforward and simple.

Jonathan
  • 5,027
  • 39
  • 48
11

I'm using AOP and Java annotations. There is a ready-made mechanism in jcabi-aspects (I'm a developer):

@RetryOnFailure(attempts = 3, delay = 1, unit = TimeUnit.SECONDS)
public void establishConnection() {
  this.connection = newConnection();
}

ps. You can also try RetryScalar from Cactoos.

yegor256
  • 102,010
  • 123
  • 446
  • 597
  • I always wanted to create something like this! I'll try to contribute! Why not Apache License 2.0 though? – Marsellus Wallace Aug 02 '13 at 21:42
  • @yegor256 where can I download the jar? – Will Aug 07 '14 at 22:11
  • I know this is an old posting but I'm trying to use the jcabi-aspects since I like the clean interface with the annotations. But I'm having trouble with it since there are a number of critical vulnerabilities being flagged in the required dependencies by SonaType/Nexus. Is there any possibility of updating it to get past these? – jwh20 Sep 02 '21 at 12:56
6

You can try spring-retry, it has a clean interface and it's easy to use.

Example:

 @Retryable(maxAttempts = 4, backoff = @Backoff(delay = 500))
 public void establishConnection() {
    this.connection = newConnection();
 } 

In case of exception, it will retry (call) up to 4 times the method establishConnection() with a backoff policy of 500ms

db80
  • 4,157
  • 1
  • 38
  • 38
  • 1
    As I understand @Retryable will works only in the Spring application https://www.baeldung.com/spring-retry – Valeriy K. Dec 06 '19 at 08:45
3

You can also create a wrapper function that just does a loop over the intended operation and when is done just break out of the loop.

public static void main(String[] args) {
    retryMySpecialOperation(7);
}

private static void retryMySpecialOperation(int retries) {
    for (int i = 1; i <= retries; i++) {
        try {
            specialOperation();
            break;
        }
        catch (Exception e) {
            System.out.println(String.format("Failed operation. Retry %d", i));
        }
    }
}

private static void specialOperation() throws Exception {
    if ((int) (Math.random()*100) % 2 == 0) {
        throw new Exception("Operation failed");
    }
    System.out.println("Operation successful");
}
klusht
  • 31
  • 3
2

If you are using java 8, this may helps.

import java.util.function.Supplier;

public class Retrier {
public static <T> Object retry(Supplier<T> function, int retryCount) throws Exception {
     while (0<retryCount) {
        try {
            return function.get();
        } catch (Exception e) {
            retryCount--;
            if(retryCount == 0) {
                throw e;
            }
        }
    }
    return null;
}

public static void main(String[] args) {
    try {
        retry(()-> {
            System.out.println(5/0);
            return null;
        }, 5);
    } catch (Exception e) {
        System.out.println("Exception : " + e.getMessage());
    }
}
}

Thanks,

Praveen R.

Ravipati Praveen
  • 446
  • 1
  • 7
  • 28
1

I'm using retry4j library. Test code example:

public static void main(String[] args) {
    Callable<Object> callable = () -> {
        doSomething();
        return null;
    };

    RetryConfig config = new RetryConfigBuilder()
            .retryOnAnyException()
            .withMaxNumberOfTries(3)
            .withDelayBetweenTries(5, ChronoUnit.SECONDS)
            .withExponentialBackoff()
            .build();

    new CallExecutorBuilder<>().config(config).build().execute(callable);
}

public static void doSomething() {
    System.out.println("Trying to connect");
    // some logic
    throw new RuntimeException("Disconnected"); // init error
    // some logic
}
Valeriy K.
  • 2,616
  • 1
  • 30
  • 53
1

Here's a another approach to perform the retry. No libraries, no annotations, no extra implementations. Import java.util.concurrent.TimeUnit;

public static void myTestFunc() {
        boolean retry = true;
        int maxRetries = 5;   //max no. of retries to be made
        int retries = 1;
        int delayBetweenRetries = 5;  // duration  between each retry (in seconds)
        int wait = 1;
    do {
        try {
            this.connection = newConnection();
            break;
        }
        catch (Exception e) {
            wait = retries * delayBetweenRetries;
            pause(wait);
            retries += 1;
            if (retries > maxRetries) {
                retry = false;
                log.error("Task failed on all of " + maxRetries + " retries");
            }
        }
    } while (retry);

}

public static void pause(int seconds) {
    long secondsVal = TimeUnit.MILLISECONDS.toMillis(seconds);

    try {
        Thread.sleep(secondsVal);
    }
    catch (InterruptedException ex) {
        Thread.currentThread().interrupt();
    }
}

}

Vishal Kharde
  • 1,553
  • 3
  • 16
  • 34
0

there is nothing special in retrying at all - take this class as example http://www.docjar.com/html/api/org/springframework/jms/listener/DefaultMessageListenerContainer.java.html As you can see even spring developers still writing code for retry-ing - line 791... there is no such special pattern AFAIK..

What i can advice to deal with resources is to take apache commons pool library - check this http://commons.apache.org/pool/apidocs/org/apache/commons/pool/impl/GenericObjectPool.html and visit http://commons.apache.org/pool

Andrey Borisov
  • 3,160
  • 18
  • 18
  • It sure seems like there is a pattern, as per the other answer. I'm just looking for a good way of wrapping my head around doing something like this in an efficient way. – Naftuli Kay Jul 27 '12 at 17:45
0

I have wrote my custom annotation. Maybe you can use this annotation.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RetryOperation {
 
    int retryCount();
 
    int waitSeconds();
}

@Slf4j
@Aspect
@Component
public class RetryOperationAspect {
 
    @Around(value = "@annotation(com.demo.infra.annotation.RetryOperation)")
    public Object retryOperation(ProceedingJoinPoint joinPoint) throws Throwable {
        Object response = null;
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        RetryOperation annotation = method.getAnnotation(RetryOperation.class);
        int retryCount = annotation.retryCount();
        int waitSeconds = annotation.waitSeconds();
        boolean successful = false;
 
        do {
            try {
                response = joinPoint.proceed();
                successful = true;
            } catch (Exception e) {
                log.error("Operation failed, retries remaining: {}", retryCount);
                retryCount--;
                if (retryCount < 0) {
                    throw e;
                }
                if (waitSeconds > 0) {
                    log.warn("Waiting for {} second(s) before next retry", waitSeconds);
                    Thread.sleep(waitSeconds * 1000L);
                }
            }
        } while (!successful);
 
        return response;
    }
}


@RetryOperation(retryCount = 5, waitSeconds = 1)
public void method() {
         
}
Mehmet Pekdemir
  • 318
  • 4
  • 11