{"id":13647178,"url":"https://github.com/nurkiewicz/async-retry","last_synced_at":"2025-04-22T02:30:41.962Z","repository":{"id":9623724,"uuid":"11551617","full_name":"nurkiewicz/async-retry","owner":"nurkiewicz","description":"Asynchronous retrying for Java 7/8","archived":true,"fork":false,"pushed_at":"2015-05-24T18:03:13.000Z","size":622,"stargazers_count":411,"open_issues_count":5,"forks_count":81,"subscribers_count":31,"default_branch":"master","last_synced_at":"2024-09-26T10:09:29.631Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://nurkiewicz.blogspot.com/2013/07/asynchronous-retry-pattern.html","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nurkiewicz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"license.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-07-20T19:00:12.000Z","updated_at":"2024-07-30T09:36:58.000Z","dependencies_parsed_at":"2022-09-13T08:40:42.388Z","dependency_job_id":null,"html_url":"https://github.com/nurkiewicz/async-retry","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nurkiewicz%2Fasync-retry","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nurkiewicz%2Fasync-retry/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nurkiewicz%2Fasync-retry/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nurkiewicz%2Fasync-retry/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nurkiewicz","download_url":"https://codeload.github.com/nurkiewicz/async-retry/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223887436,"owners_count":17219927,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T01:03:22.906Z","updated_at":"2024-11-09T21:30:15.700Z","avatar_url":"https://github.com/nurkiewicz.png","language":"Java","funding_links":[],"categories":["Java","容错组件"],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/nurkiewicz/async-retry.svg?branch=master)](https://travis-ci.org/nurkiewicz/async-retry) [![Build Status](https://drone.io/github.com/nurkiewicz/async-retry/status.png)](https://drone.io/github.com/nurkiewicz/async-retry/latest) [![Coverage Status](https://img.shields.io/coveralls/nurkiewicz/async-retry.svg)](https://coveralls.io/r/nurkiewicz/async-retry) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.nurkiewicz.asyncretry/asyncretry/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.nurkiewicz.asyncretry/asyncretry)\n\n# Asynchronous retry pattern\n\nWhen you have a piece of code that often fails and must be retried, this Java 7/8 library provides rich and unobtrusive API with fast and scalable solution to this problem:\n\n```java\nScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();\nRetryExecutor executor = new AsyncRetryExecutor(scheduler).\n\tretryOn(SocketException.class).\n\twithExponentialBackoff(500, 2).     //500ms times 2 after each retry\n\twithMaxDelay(10_000).               //10 seconds\n\twithUniformJitter().                //add between +/- 100 ms randomly\n\twithMaxRetries(20);\n```\n\nYou can now run arbitrary block of code and the library will retry it for you in case it throws `SocketException`:\n\n```java\nfinal CompletableFuture\u003cSocket\u003e future = executor.getWithRetry(() -\u003e\n\t\tnew Socket(\"localhost\", 8080)\n);\n\nfuture.thenAccept(socket -\u003e\n\t\tSystem.out.println(\"Connected! \" + socket)\n);\n```\n\nPlease look carefully! `getWithRetry()` does not block. It returns `CompletableFuture` immediately and invokes given function asynchronously. You can listen for that `Future` or even for multiple futures at once and do other work in the meantime. So what this code does is: trying to connect to `localhost:8080` and if it fails with `SocketException` it will retry after 500 milliseconds (with some random jitter), doubling delay after each retry, but not above 10 seconds.\n\nEquivalent but more concise syntax:\n\n```java\nexecutor.\n\t\tgetWithRetry(() -\u003e new Socket(\"localhost\", 8080)).\n\t\tthenAccept(socket -\u003e System.out.println(\"Connected! \" + socket));\n```\n\nThis is a sample output that you might expect:\n\n    TRACE | Retry 0 failed after 3ms, scheduled next retry in 508ms (Sun Jul 21 21:01:12 CEST 2013)\n    java.net.ConnectException: Connection refused\n    \tat java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0-ea]\n    \t//...\n    \n    TRACE | Retry 1 failed after 0ms, scheduled next retry in 934ms (Sun Jul 21 21:01:13 CEST 2013)\n    java.net.ConnectException: Connection refused\n    \tat java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0-ea]\n    \t//...\n    \n    TRACE | Retry 2 failed after 0ms, scheduled next retry in 1919ms (Sun Jul 21 21:01:15 CEST 2013)\n    java.net.ConnectException: Connection refused\n    \tat java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0-ea]\n    \t//...\n    \n    TRACE | Successful after 2 retries, took 0ms and returned: Socket[addr=localhost/127.0.0.1,port=8080,localport=46332]\n\n    Connected! Socket[addr=localhost/127.0.0.1,port=8080,localport=46332]\n    \nImagine you connect to two different systems, one is *slow*, second *unreliable* and fails often:\n\n```java\nCompletableFuture\u003cString\u003e stringFuture = executor.getWithRetry(ctx -\u003e unreliable());\nCompletableFuture\u003cInteger\u003e intFuture = executor.getWithRetry(ctx -\u003e slow());\n\nstringFuture.thenAcceptBoth(intFuture, (String s, Integer i) -\u003e {\n\t//both done after some retries\n});\n```\n\n`thenAcceptBoth()` callback is executed asynchronously when both slow and unreliable systems finally reply without any failure. Similarly (using `CompletableFuture.acceptEither()`) you can call two or more unreliable servers asynchronously at the same time and be notified when the first one succeeds after some number of retries.\n\nI can't emphasize this enough - retries are executed asynchronously and effectively use thread pool, rather than sleeping blindly.\n\n## Rationale\n\nOften we are forced to [retry](http://servicedesignpatterns.com/WebServiceInfrastructures/IdempotentRetry) given piece of code because it failed and we must try again, typically with a small delay to spare CPU. This requirement is quite common and there are few ready-made generic implementations with [retry support in Spring Batch](http://static.springsource.org/spring-batch/reference/html/retry.html) through [`RetryTemplate`](http://static.springsource.org/spring-batch/2.1.x/apidocs/org/springframework/batch/retry/support/RetryTemplate.html) class being best known. But there are few other, quite similar approaches ([[1]](http://fahdshariff.blogspot.no/2009/08/retrying-operations-in-java.html), [[2]](https://github.com/Ninja-Squad/ninja-core/tree/master/src/main/java/com/ninja_squad/core/retry)). All of these attempts (and I bet many of you implemented similar tool yourself!) suffer the same issue - they are blocking, thus wasting a lot of resources and not scaling well.\n\nThis is not bad *per se* because it makes programming model much simpler - the library takes care of retrying and you simply have to wait for return value longer than usual. But not only it creates leaky abstraction (method that is typically very fast suddenly becomes slow due to retries and delay), but also wastes valuable threads since such facility will spend most of the time sleeping between retries. Therefore [`Async-Retry`](https://github.com/nurkiewicz/async-retry) utility was created, targeting **Java 8** (with [Java 7 backport](https://github.com/nurkiewicz/async-retry/tree/java7) existing) and addressing issues above.\n\nThe main abstraction is [`RetryExecutor`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/RetryExecutor.java) that provides simple API:\n\n```java\npublic interface RetryExecutor {\n\n\tCompletableFuture\u003cVoid\u003e doWithRetry(RetryRunnable action);\n\n\t\u003cV\u003e CompletableFuture\u003cV\u003e getWithRetry(Callable\u003cV\u003e task);\n\n\t\u003cV\u003e CompletableFuture\u003cV\u003e getWithRetry(RetryCallable\u003cV\u003e task);\n\n\t\u003cV\u003e CompletableFuture\u003cV\u003e getFutureWithRetry(RetryCallable\u003cCompletableFuture\u003cV\u003e\u003e task);\n}\n```\n\nDon't worry about [`RetryRunnable`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/function/RetryRunnable.java) and [`RetryCallable`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/function/RetryCallable.java) - they allow checked exceptions for your convenience and most of the time we will use lambda expressions anyway.\n\nPlease note that it returns [`CompletableFuture`](http://nurkiewicz.blogspot.no/2013/05/java-8-definitive-guide-to.html). We no longer pretend that calling faulty method is fast. If the library encounters an exception it will retry our block of code with preconfigured backoff delays. The invocation time will sky-rocket from milliseconds to several seconds. `CompletableFuture` clearly indicates that. Moreover it's not a dumb [`java.util.concurrent.Future`](http://nurkiewicz.blogspot.no/2013/02/javautilconcurrentfuture-basics.html) we all know - [`CompletableFuture` in Java 8 is very powerful](http://nurkiewicz.blogspot.no/2013/05/java-8-completablefuture-in-action.html) and most importantly - non-blocking by default.\n\nIf you need blocking result after all, just call `.get()` on `Future` object.\n\n## Basic API\n\nThe API is very simple. You provide a block of code and the library will run it multiple times until it returns normally rather than throwing an exception. It may also put configurable delays between retries:\n\n```java\nRetryExecutor executor = //see \"Creating an instance of RetryExecutor\" below\n\nexecutor.getWithRetry(() -\u003e new Socket(\"localhost\", 8080));\n```\n\nReturned `CompletableFuture\u003cSocket\u003e` will be resolved once connecting to `localhost:8080` succeeds. Optionally we can consume [`RetryContext`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/RetryContext.java) to get extra context like which retry is currently being executed:\n\n```java\nexecutor.\n\tgetWithRetry(ctx -\u003e new Socket(\"localhost\", 8080 + ctx.getRetryCount())).\n\tthenAccept(System.out::println);\n```\n\nThis code is more clever than it looks. During first execution `ctx.getRetryCount()` returns `0`, therefore we try to connect to `localhost:8080`. If this fails, next retry will try `localhost:8081` (`8080 + 1`) and so on. And if you realize that all of this happens asynchronously you can scan ports of several machines and be notified about first responding port on each host:\n\n```java\nArrays.asList(\"host-one\", \"host-two\", \"host-three\").\n\tstream().\n\tforEach(host -\u003e\n\t\texecutor.\n\t\t\tgetWithRetry(ctx -\u003e new Socket(host, 8080 + ctx.getRetryCount())).\n\t\t\tthenAccept(System.out::println)\n\t);\n```\n\nFor each host `RetryExecutor` will attempt to connect to port 8080 and retry with higher ports. \n\n`getFutureWithRetry()` requires special attention. I you want to retry method that already returns `CompletableFuture\u003cV\u003e`: e.g. result of asynchronous HTTP call:\n\n```java\nprivate CompletableFuture\u003cString\u003e asyncHttp(URL url) { /*...*/}\n\n//...\n\nfinal CompletableFuture\u003cCompletableFuture\u003cString\u003e\u003e response =\n\texecutor.getWithRetry(ctx -\u003e\n\t\tasyncHttp(new URL(\"http://example.com\")));\n```\n\nPassing `asyncHttp()` to `getWithRetry()` will yield `CompletableFuture\u003cCompletableFuture\u003cV\u003e\u003e`. Not only it's awkward to work with, but also broken. The library will barely call `asyncHttp()` and retry only if it fails, but not if returned `CompletableFuture\u003cString\u003e` fails. The solution is simple:\n\n```java\nfinal CompletableFuture\u003cString\u003e response =\n\texecutor.getFutureWithRetry(ctx -\u003e\n\t\tasyncHttp(new URL(\"http://example.com\")));\n```\n\nIn this case `RetryExecutor` will understand that whatever was returned from `asyncHttp()` is actually just a `Future` and will (asynchronously) wait for result or failure. Speaking of which, in some cases despite retrying `RetryExecutor` will fail to obtain successful result. In general there are three possible outcomes of returned `CompletableFuture`:\n\n1. *successful* - (possibly after some number of retries) - when our function eventually returns rather than throws\n\n2. *exceptional* due to excessive retries - if you configure finite number of retries, `RetryExecutor` will eventually give up. In that case `Future` is completed exceptionally, providing last encountered exception\n\n3. *exceptional* due to exception that should not be retried (see e.g. `abortOn()` and `abortIf()`) - just as above last encountered exception completes `Future`.\n\nYou can handle all these cases with the following by using [`CompletableFuture.whenComplete()`](http://download.java.net/lambda/b102/docs/api/java/util/concurrent/CompletableFuture.html#whenComplete(java.util.function.BiConsumer)) or [`CompletableFuture.handle()`](http://download.java.net/lambda/b102/docs/api/java/util/concurrent/CompletableFuture.html#handle(java.util.function.BiFunction)):\n\n```java\nexecutor.\n\tgetWithRetry(() -\u003e new Socket(\"localhost\", 8080)).\n\twhenComplete((socket, error) -\u003e {\n\t\tif (socket != null) {\n\t\t\t//connected OK, proceed\n\t\t} else {\n\t\t\tlog.error(\"Can't connect, last error:\", error);\n\t\t}\n\t});\n```\n\n## Configuration options\n\nIn general there are two important factors you can configure: [`RetryPolicy`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/policy/RetryPolicy.java) that controls whether next retry attempt should be made and [`Backoff`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/backoff/Backoff.java) - that optionally adds delay between subsequent retry attempts.\n\nBy default `RetryExecutor` repeats user task infinitely on every `\tThrowable` and adds 1 second delay between retry attempts.\n\n### Creating an instance of `RetryExecutor`\n\nDefault implementation of `RetryExecutor` is [`AsyncRetryExecutor`](https://github.com/nurkiewicz/async-retry/blob/master/src/main/java/com/nurkiewicz/asyncretry/AsyncRetryExecutor.java) which you can create directly:\n\n```java\nScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();\n\nRetryExecutor executor = new AsyncRetryExecutor(scheduler);\n\n//...\n\nscheduler.shutdownNow();\n```\n\nThe only required dependency is standard [`ScheduledExecutorService` from JDK](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledExecutorService.html). One thread is enough in many cases but if you want to concurrently handle retries of hundreds or more tasks, consider increasing the pool size.\n\nNotice that the `AsyncRetryExecutor` does not take care of shutting down the `ScheduledExecutorService`. This is a conscious design decision which will be explained later.\n\n`AsyncRetryExecutor` has few other constructors but most of the time altering the behaviour of this class is most convenient with calling chained `with*()` methods. You will see plenty of examples written this way. Later on we will simply use `executor` reference without defining it. Assume it's of `RetryExecutor` type.\n\n### Retrying policy\n\n#### Exception classes\n\nBy default every `Throwable` (except special `AbortRetryException`) thrown from user task causes retry. Obviously this is configurable. For example in JPA you may want to retry a transaction that failed due to [`OptimisticLockException`](http://docs.oracle.com/javaee/6/api/javax/persistence/OptimisticLockException.html) - but every other exception should fail immediately:\n\n```java\nexecutor.\n\tretryOn(OptimisticLockException.class).\n\twithNoDelay().\n\tgetWithRetry(ctx -\u003e dao.optimistic());\n```\n\nWhere `dao.optimistic()` may throw `OptimisticLockException`. In that case you probably don't want any delay between retries, more on that later. If you don't like the default of retrying on every `Throwable`, just restrict that using `retryOn()`:\n\n```java\nexecutor.retryOn(Exception.class)\n```\n\nOf course the opposite might also be desired - to abort retrying and fail immediately in case of certain exception being thrown rather than retrying. It's that simple:\n\n```java\nexecutor.\n\tabortOn(NullPointerException.class).\n\tabortOn(IllegalArgumentException.class).\n\tgetWithRetry(ctx -\u003e dao.optimistic());\n```\n\nClearly you don't want to retry `NullPointerException` or `IllegalArgumentException` as they indicate programming bug rather than transient failure. And finally you can combine both retry and abort policies. User code will retry in case of any `retryOn()` exception (or subclass) unless it should `abortOn()` specified exception. For example we want to retry every `IOException` or `SQLException` but abort if `FileNotFoundException` or `java.sql.DataTruncation` is encountered (order of declarations is irrelevant):\n\n```java\nexecutor.\n\tretryOn(IOException.class).\n\tabortOn(FileNotFoundException.class).\n\tretryOn(SQLException.class).\n\tabortOn(DataTruncation.class).\n\tgetWithRetry(ctx -\u003e dao.load(42));\n```\n#### Exception predicates\n\nIf this is not enough you can provide custom predicate that will be invoked on each failure:\n\n```java\nexecutor.\n\tabortIf(throwable -\u003e\n\t\tthrowable instanceof SQLException \u0026\u0026\n\t\t\t\tthrowable.getMessage().contains(\"ORA-00911\")\t\n\t).\n\tretryIf(t -\u003e t.getCause() != null);\n```\n\nIf any of `abortIf()` or `retryIf()` predicates return `true` task is aborted or retried respectively. Keep in mind that `abortIf()`/`retryIf()` take priority over `abortOn()`/`retryOn()` thus the following piece of code will retry on `FileNotFoundException(\"Access denied\")`:\n\n```java\nexecutor.\n\t\tabortOn(FileNotFoundException.class).\n\t\tabortOn(NullPointerException.class).\n\t\tretryIf(e -\u003e e.getMessage().contains(\"denied\"))\n``` \n\nIf more than one `abortIf()` predicate passes as well as more than one `retryIf()` predicate then computation is aborted.\n\n#### Max number of retries\n\nAnother way of interrupting retrying \"loop\" (remember that this process is asynchronous, there is no blocking *loop*) is by specifying maximum number of retries:\n\n```java\nexecutor.withMaxRetries(5)\n```\n\nIn rare cases you may want to disable retries and barely take advantage from asynchronous execution. In that case try:\n\n```java\nexecutor.dontRetry()\n```\n\nMax number of retries takes precedence over `*On()` and `*If()` family of methods.\n\n### Delays between retries (backoff)\n\nRetrying immediately after failure is sometimes desired (see `OptimisticLockException` example) but in most cases it's a bad idea. If you can't connect to external system, waiting a little bit before next attempt sounds reasonably. You save CPU, bandwidth and other server's resources. But there are quite a few options to consider: \n\n* should we retry with constant intervals or [increase delay after each failure](http://en.wikipedia.org/wiki/Exponential_backoff)?\n\n* should there be a lower and upper limit on waiting time?\n\n* should we add random \"jitter\" to delay times to spread retries of many tasks in time?\n\nThis library answers all these questions.\n\n#### Fixed interval between retries\n\nBy default each retry is preceded by 1 second waiting time. So if initial attempt fails, first retry will be executed after 1 second. Of course we can change that default, e.g. to 200 milliseconds:\n\n```java\nexecutor.withFixedBackoff(200)\n```\n\nIf we are already here, by default backoff is applied after executing user task. If user task itself consumes some time, retries will be less frequent. For example with retry delay of 200ms and average time it takes before user task fails at about 50ms `RetryExecutor` will retry about 4 times per second (50ms + 200ms). However if you want to keep retry frequency at more predictable level you can use `fixedRate` flag:\n\n```java\nexecutor.\n\twithFixedBackoff(200).\n\twithFixedRate()\n```\n\nThis is similar to \"fixed rate\" vs. \"fixed delay\" approaches in [`ScheduledExecutorService`](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledExecutorService.html). BTW don't expect `RetryExecutor` to be very precise, it does it's best but it heavily depends on aforementioned `ScheduledExecutorService` accuracy.\n\n#### Exponentially growing intervals between retries\n\nIt's probably an active research subject, but in general you may wish to expand retry delay over time, assuming that if the user task fails several times we should try less frequently. For example let's say we start with 100ms delay until first retry attempt is made but if that one fails as well, we should wait two times more (200ms). And later 400ms, 800ms... You get the idea:\n\n```java\nexecutor.withExponentialBackoff(100, 2)\n```\n\nThis is an exponential function that can grow very fast. Thus it's useful to set maximum backoff time at some reasonable level, e.g. 10 seconds:\n\n```java\nexecutor.\n\twithExponentialBackoff(100, 2).\n\twithMaxDelay(10_000)      //10 seconds\n```\n\n#### Random jitter\n\nOne phenomena often observed during major outages is that systems tend to synchronize. Imagine a busy system that suddenly stops responding. Hundreds or thousands of requests fail and are retried. It depends on your backoff but by default all these requests will retry exactly after one second producing huge wave of traffic at one point in time. Finally such failures are propagated to other systems that, in turn, synchronize as well.\n\nTo avoid this problem it's useful to spread retries over time, flattening the load. A simple solution is to add random jitter to delay time so that not all request are scheduled for retry at the exact same time. You have choice between uniform jitter (random value from -100ms to 100ms):\n\n```java\nexecutor.withUniformJitter(100)     //ms\n```\n\n...and proportional jitter, multiplying delay time by random factor, by default between 0.9 and 1.1 (10%):\n\n```java\nexecutor.withProportionalJitter(0.1)        //10%\n```\n\nYou may also put hard lower limit on delay time to avoid to short retry times being scheduled:\n\n```java\nexecutor.withMinDelay(50)   //ms\n```\n\n#### No initial delay\n\nThere are cases when you want to run first retry immediately and fall back to configured backoff mechanism for subsequent requests. You can use `firstRetryNoDelay()` method:\n\n```java\nexecutor\n\t.withExponentialBackoff(100, 2)\n\t.firstRetryNoDelay();\nCompletableFuture\u003cString\u003e future = executor.getWithRetry(this::someTask);\n```\n\nWith the above configuration first retry is executed immediately, second retry after 100 ms, third after 200 ms and so on. Without `firstRetryNoDelay()` first retry would be delayed by 100 ms already.\n\n## Implementation details\n\nThis library was built with Java 8 in mind to take advantage of lambdas and new `CompletableFuture` abstraction (but [Java 7 port with Guava dependency exists](https://github.com/nurkiewicz/async-retry/tree/java7)). It uses `ScheduledExecutorService` underneath to run tasks and schedule retries in the future - which allows best thread utilization.\n\nBut what is really interesting is that the whole library is fully immutable, there is no single mutable field, at all. This might be counter-intuitive at first, take for example this trivial code sample:\n\n```java\nScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();\n\nAsyncRetryExecutor first = new AsyncRetryExecutor(scheduler).\n\tretryOn(Exception.class).\n\twithExponentialBackoff(500, 2);\n\nAsyncRetryExecutor second = first.abortOn(FileNotFoundException.class);\n\nAsyncRetryExecutor third = second.withMaxRetries(10);\n```\n\nIt might seem that all `with*()` methods or `retryOn()`/`abortOn()` mutate existing executor. But that's not the case, each configuration change **creates new instance**, leaving the old one untouched. So for example while `first` executor will retry on `FileNotFoundException`, the `second` and `third` won't. However they all share the same `scheduler`. This is the reason why `AsyncRetryExecutor` does not shut down `ScheduledExecutorService` (it doesn't even have any `close()` method). Since we have no idea how many copies of `AsyncRetryExecutor` exist pointing to the same scheduler, we don't even try to manage its lifecycle. However this is typically not a problem (see *Spring integration* below).\n\nYou might be wondering, why such an awkward design decision? There are three reasons:\n\n* when writing a concurrent code immutability can greatly reduce risk of multi-threading bugs. For example `RetryContext` holds number of retries. But instead of mutating it we simply create new instance (copy) with incremented but `final` counter. No race condition or visibility can ever occur.\n\n* if you are given an existing `RetryExecutor` which is almost exactly what you want but you need one minor tweak, you simply call `executor.with...()` and get a fresh copy. You don't have to worry about other places where the same executor was used (see: *Spring integration* for further examples)\n\n* functional programming and immutable data structures are *sexy* these days ;-).\n\nN.B.: `AsyncRetryExecutor` is **not** marked `final`, does you can break immutability by subclassing it and adding mutable state. Please don't do this, subclassing is only permitted to alter behaviour.\n\n## Dependencies\n\nThis library requires Java 8 and [SLF4J](http://www.slf4j.org/) for logging. Java 7 port additionally depends on [Guava](http://code.google.com/p/guava-libraries/).\n\n## Spring integration\n\nIf you are just about to use `RetryExecutor` in Spring - feel free, but the configuration API might not work for you. Spring promotes (or used to promote) the convention of mutable services with plenty of setters. In XML you define bean and invoke setters (via `\u003cproperty name=\"...\"/\u003e`) on it. This convention assumes the existence of mutating setters. But I found this approach error-prone and counter-intuitive under some circumstances.\n\nLet's say we globally defined [`org.springframework.transaction.support.TransactionTemplate`](http://static.springsource.org/spring/docs/current/javadoc-api/org/springframework/transaction/support/TransactionTemplate.html) bean and injected it in multiple places. Great. Now there is this one single request that requires slightly different timeout:\n\n```java\n@Autowired\nprivate TransactionTemplate template;\n```\n\nand later in the same class:\n    \n```java\nfinal int oldTimeout = template.getTimeout();\ntemplate.setTimeout(10_000);\n//do the work\ntemplate.setTimeout(oldTimeout);\n```\n\nThis code is wrong on so many levels! First of all if something fails we never restore `oldTimeout`. OK, `finally` to the rescue. But also notice how we changed global, shared `TransactionTemplate` instance. Who knows how many other beans and threads are just about to use it, unaware of changed configuration?\n\nAnd even if you do want to globally change the transaction timeout, fair enough, but it's still wrong way to do this. `private timeout` field is not `volatile` and thus changes made to it may or may not be visible to other threads. What a mess! The same problem appears with many other classes like [`JmsTemplate`](http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/jms/core/JmsTemplate.html).\n\nYou see where I'm going? Just create one, immutable service class and safely adjust it by creating copies whenever you need it. And using such services is equally simple these days:\n\n```java\n@Configuration\nclass Beans {\n\n\t@Bean\n\tpublic RetryExecutor retryExecutor() {\n\t\treturn new AsyncRetryExecutor(scheduler()).\n\t\t\tretryOn(SocketException.class).\n\t\t\twithExponentialBackoff(500, 2);\n\t}\n\n\t@Bean(destroyMethod = \"shutdownNow\")\n\tpublic ScheduledExecutorService scheduler() {\n\t\treturn Executors.newSingleThreadScheduledExecutor();\n\t}\n\n}\n```\n\nHey! It's 21st century, we don't need XML in Spring any more. Bootstrap is simple as well:\n\n```java\nfinal ApplicationContext context = new AnnotationConfigApplicationContext(Beans.class);\nfinal RetryExecutor executor = context.getBean(RetryExecutor.class);\n//...\ncontext.close();\n```\n\nAs you can see integrating modern, immutable services with Spring is just as simple. BTW if you are not prepared for such a big change when designing your own services, at least consider [constructor injection](http://nurkiewicz.blogspot.no/2011/09/evolution-of-spring-dependency.html).\n\n## Maturity\n\nThis library is covered with a strong battery of unit tests ([![Build Status](https://travis-ci.org/nurkiewicz/async-retry.svg?branch=master)](https://travis-ci.org/nurkiewicz/async-retry)). However it wasn't yet used in any production code and the API is subject to change. Of course you are encouraged to submit [bugs, feature requests](https://github.com/nurkiewicz/async-retry/issues) and [pull requests](https://github.com/nurkiewicz/async-retry/pulls). It was developed with Java 8 in mind but [Java 7 backport](https://github.com/nurkiewicz/async-retry/tree/java7) exists with slightly more verbose API and mandatory Guava dependency ([`ListenableFuture`](http://nurkiewicz.blogspot.no/2013/02/listenablefuture-in-guava.html) instead of [`CompletableFuture` from Java 8](http://nurkiewicz.blogspot.no/2013/05/java-8-definitive-guide-to.html)).\n\n## Using\n\n### Maven\n\nThis library is available in [Maven Central Repository](http://search.maven.org):\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.nurkiewicz.asyncretry\u003c/groupId\u003e\n    \u003cartifactId\u003easyncretry\u003c/artifactId\u003e\n    \u003cversion\u003e0.0.7\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n\n### Maven (Java 7)\n\nBecause backport to Java 7 has different API, it is maintained in a [separate branch](https://github.com/nurkiewicz/async-retry/tree/java7). It is also deployed to Maven Central under:\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.nurkiewicz.asyncretry\u003c/groupId\u003e\n    \u003cartifactId\u003easyncretry-jdk7\u003c/artifactId\u003e\n    \u003cversion\u003e0.0.7\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### Troubleshooting: `invalid target release: 1.8` during maven build\n\nIf you see this error message during maven build:\n\n\t[INFO] BUILD FAILURE\n\t...\n\t[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project lazyseq: \n\tFatal error compiling: invalid target release: 1.8 -\u003e [Help 1]\n\nit means you are not compiling using Java 8. [Download JDK 8 with lambda support](https://jdk8.java.net/lambda/) and let maven use it:\n\n\t$ export JAVA_HOME=/path/to/jdk8\n\n\n## Version history\n\n### 0.0.7 (24-05-2015)\n\n* [`firstRetryNoDelay()` method](https://github.com/nurkiewicz/async-retry/issues/6)\n* [Add withNoRetries() and withInfiniteRetries()](https://github.com/nurkiewicz/async-retry/issues/5)\n* Fixed [*Errors from predicates are silently ignored*](https://github.com/nurkiewicz/async-retry/pull/7)\n\n### 0.0.6 (26-11-2014)\n\n* [`SyncRetryExecutor`](https://github.com/nurkiewicz/async-retry/blob/0.0.6/src/main/java/com/nurkiewicz/asyncretry/SyncRetryExecutor.java) convenience class.\n\n### 0.0.5 (31-05-2014)\n\n* Bringing back Java 7 support\n\n### 0.0.4 (30-05-2014)\n\n* First official release into [Maven Central repository](http://central.maven.org/maven2/com/nurkiewicz/asyncretry/asyncretry).\n\n### 0.0.3 (05-01-2014)\n\n* Fixed [#3 *RetryOn ignored due to wrong command order*](https://github.com/nurkiewicz/async-retry/issues/3)\n* `AbortRetryException` class was moved from `com.nurkiewicz.asyncretry.policy.exception` to `com.nurkiewicz.asyncretry.policy`\n* Java 7 backport is no longer maintained starting from this version\n\n### 0.0.2 (28-07-2013)\n\n* Ability to specify multiple exception classes in `retryOn()`/`abortON()` using varargs\n\n### 0.0.1 (23-07-2013)\n\n* Initial revision\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnurkiewicz%2Fasync-retry","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnurkiewicz%2Fasync-retry","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnurkiewicz%2Fasync-retry/lists"}