Java: how to handle retries without copy code?

I have several cases where I have to do reprocessing for databases and network operations. Wherever I do this, I have the following type of code:

for (int iteration = 1; ; iteration++) { try { data = doSomethingUseful(data); break; } catch (SomeException | AndAnotherException e) { if (iteration == helper.getNumberOfRetries()) { throw e; } else { errorReporter.reportError("Got following error for data = {}. Continue trying after delay...", data, e); utilities.defaultDelayForIteration(iteration); handleSpecificCase(data); } } } 

The problem is that this code template is copied to all classes. Which is really bad. I can’t figure out how to get rid of this copy-paste pattern for-break-catch, since I usually get another exception for processing, I want to log data that I failed with (usually also different ways).

Is there a good way to avoid this copy-paste in Java 7?

Edit: I use guice to inject dependencies. I checked the exceptions. Instead of several data, there can be several variables, and they have different types.

Edit2 : AOP approach looks the most promising for me.

+8
java copy-paste exception
source share
7 answers

Out of my hand, I can think of two different approaches:

If differences in exception handling can be expressed declaratively, you can use AOP to weave exception handling code around your methods. Then your business code might look like this:

 @Retry(times = 3, loglevel = LogLevel.INFO) List<User> getActiveUsers() throws DatabaseException { // talk to the database } 

The advantage is that it’s very easy to add repetition behavior to the method, the disadvantage is the difficulty of weaving advice (which you need to implement only once. If you use the dependency injection library, this will most likely offer method interception support).

Another approach is to use a command template:

 abstract class Retrieable<I,O> { private final LogLevel logLevel; protected Retrieable(LogLevel loglevel) { this.logLevel = loglevel; } protected abstract O call(I input); // subclasses may override to perform custom logic. protected void handle(RuntimeException e) { // log the exception. } public O execute(I input) { for (int iteration = 1; ; iteration++) { try { return call(input); } catch (RuntimeException e) { if (iteration == helper.getNumberOfRetries()) { throw e; } else { handle(); utilities.defaultDelayForIteration(iteration); } } } } } 

The problem with the command template is the method arguments. You are limited to one parameter, and generics are rather inconvenient for the caller. In addition, it will not work with checked exceptions. On the plus side, no fancy AOP stuff :-)

+5
source share

As already mentioned, AOP and Java annotations are a good option. I would recommend using the reading mechanism from the jcabi aspects :

 @RetryOnFailure(attempts = 2, delay = 10, verbose = false) public String load(URL url) { return url.openConnection().getContent(); } 

Read also this blog entry: http://www.yegor256.com/2014/08/15/retry-java-method-on-exception.html

+4
source share

I implemented the RetryLogic class, which provides repeated retry logic and supports parameters, because the code to be repeated is in the delegate passed in.

 /** * Generic retry logic. Delegate must throw the specified exception type to trigger the retry logic. */ public class RetryLogic<T> { public static interface Delegate<T> { T call() throws Exception; } private int maxAttempts; private int retryWaitSeconds; @SuppressWarnings("rawtypes") private Class retryExceptionType; public RetryLogic(int maxAttempts, int retryWaitSeconds, @SuppressWarnings("rawtypes") Class retryExceptionType) { this.maxAttempts = maxAttempts; this.retryWaitSeconds = retryWaitSeconds; this.retryExceptionType = retryExceptionType; } public T getResult(Delegate<T> caller) throws Exception { T result = null; int remainingAttempts = maxAttempts; do { try { result = caller.call(); } catch (Exception e){ if (e.getClass().equals(retryExceptionType)) { if (--remainingAttempts == 0) { throw new Exception("Retries exausted."); } else { try { Thread.sleep((1000*retryWaitSeconds)); } catch (InterruptedException ie) { } } } else { throw e; } } } while (result == null && remainingAttempts > 0); return result; } } 

The following is an example of use. The code to be repeated is within the calling method.

 private MyResultType getDataWithRetry(final String parameter) throws Exception { return new RetryLogic<MyResultType>(5, 15, Exception.class).getResult(new RetryLogic.Delegate<MyResultType> () { public MyResultType call() throws Exception { return dataLayer.getData(parameter); }}); } 

If you want to retry only when a specific type of exception occurs (and all other types of exceptions fail), the RetryLogic class supports the exception class parameter.

+3
source share

Make your doSomething implement an interface like Runable , and create a method containing your code above, replacing doSomething with interface.run(data)

+2
source share

take a look at: this snooze utility

this method should work in most cases:

 public static <T> T executeWithRetry(final Callable<T> what, final int nrImmediateRetries, final int nrTotalRetries, final int retryWaitMillis, final int timeoutMillis) 

you can easily implement the aspect using this utility to make it even less code.

+1
source share

The extension of the approach discussed already, how about something like this (there is no IDE on this netbook, so consider this as a pseudo-code ...)

 // generics left as an exercise for the reader... public Object doWithRetry(Retryable r){ for (int iteration = 1; ; iteration++) { try { return r.doSomethingUseful(data); } catch (Exception e) { if (r.isRetryException(e)) { if(r.tooManyRetries(i){ throw e; } } else { r.handleOtherException(e); } } } 
0
source share

One thing I would like to add. Most of the exceptions (99.999%) mean that something is very wrong with your code or environment that requires the attention of admins. If your code cannot connect to the database, this is probably a misconfigured environment, it makes little sense to repeat it to find out that it does not work the 3rd, 4th or 5th time. If you throw an exception because the person did not give a valid credit card number, retrying will not magically populate the credit card number.

The only situations that require a repeated attempt to remotely work are when the system is very stressed and ends from time to time, but in this situation, the repetition logic will probably cause more stress than less (3 times for 3 attempts in each transaction). But this is what systems do to reduce demand (see apollo landing module mission story). When the system is asked to do more than it can, it starts to drop tasks, and timeouts are a signal that the system is stressed (or poorly written). You would be in a much better situation if you simply increased the capacity of your system (add more bars, larger servers, more servers, improve algorithms, scale it!).

Another situation would be if you used optimistic locking, and you can somehow restore and automatically combine the two versions of the object. Although I saw this before I cautioned this approach, it can be done for simple objects that can be combined without conflict in 100% of cases.

The logic of most exceptions should be captured at the appropriate level (very important), make sure your system is in a good coordinated state (for example, rollback transactions, closed files, etc.), write it down, tell the user that it does not work.

But I will laugh at this idea and try to give a good framework (well, because it's fun, like the pleasure of a crossword puzzle).

 // client code - what you write a lot public class SomeDao { public SomeReturn saveObject( final SomeObject obj ) throws RetryException { Retry<SomeReturn> retry = new Retry<SomeReturn>() { public SomeReturn execute() throws Exception { try { // doSomething return someReturn; } catch( SomeExpectedBadExceptionNotWorthRetrying ex ) { throw new NoRetryException( ex ); // optional exception block } } } return retry.run(); } } // framework - what you write once public abstract class Retry<T> { public static final int MAX_RETRIES = 3; private int tries = 0; public T execute() throws Exception; public T run() throws RetryException { try { return execute(); } catch( NoRetryException ex ) { throw ex; } catch( Exception ex ) { tries++; if( MAX_RETRIES == tries ) { throw new RetryException("Maximum retries exceeded", ex ); } else { return run(); } } } } 
0
source share

All Articles