{"id":15043060,"url":"https://github.com/elennick/retry4j","last_synced_at":"2025-10-04T05:31:31.298Z","repository":{"id":57719147,"uuid":"44877091","full_name":"elennick/retry4j","owner":"elennick","description":"Lightweight Java library for retrying unreliable logic - DEPRECATED","archived":true,"fork":false,"pushed_at":"2023-06-21T01:28:50.000Z","size":294,"stargazers_count":205,"open_issues_count":15,"forks_count":27,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-07-29T15:31:35.288Z","etag":null,"topics":["backoff-strategy","deprecated","java","java-8","java-library","java8","retries","retry","retry-library","retry-strategies"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/elennick.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-10-24T17:37:46.000Z","updated_at":"2025-07-18T14:02:55.000Z","dependencies_parsed_at":"2025-01-23T03:31:18.776Z","dependency_job_id":"5efe94aa-bb93-4a40-991b-143e0a819caf","html_url":"https://github.com/elennick/retry4j","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/elennick/retry4j","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elennick%2Fretry4j","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elennick%2Fretry4j/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elennick%2Fretry4j/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elennick%2Fretry4j/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/elennick","download_url":"https://codeload.github.com/elennick/retry4j/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elennick%2Fretry4j/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278267476,"owners_count":25958871,"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","status":"online","status_checked_at":"2025-10-04T02:00:05.491Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["backoff-strategy","deprecated","java","java-8","java-library","java8","retries","retry","retry-library","retry-strategies"],"created_at":"2024-09-24T20:48:31.096Z","updated_at":"2025-10-04T05:31:31.021Z","avatar_url":"https://github.com/elennick.png","language":"Java","funding_links":[],"categories":["容错组件"],"sub_categories":[],"readme":"[![Coverage Status](https://coveralls.io/repos/github/elennick/retry4j/badge.svg?branch=master)](https://coveralls.io/github/elennick/retry4j?branch=master) ![Maven Central](https://img.shields.io/maven-central/v/com.evanlennick/retry4j.svg) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n## DEPRECATED\n\nRetry4j is no longer maintained and has had no releases for several years. Please feel free to fork and make your own changes/releases if you want to build upon it. Also consider checking out more modern libraries such as:\n\n* Failsafe - https://github.com/jhalterman/failsafe\n* Spring Retry - https://github.com/spring-projects/spring-retry\n* resilience4j - https://github.com/resilience4j/resilience4j\n\n## Retry4j\n\nRetry4j is a simple Java library to assist with retrying transient failure situations or unreliable code. Retry4j aims to be readable, well documented and streamlined.\n\n## Table of Contents\n\n* [Basic Code Examples](#basic-code-examples)\n    * [Handling Failures with Exceptions](#handling-failures-with-exceptions)\n    * [Handling All Results With Listeners](#handling-all-results-with-listeners)\n* [Dependencies](#dependencies)\n    * [Maven](#maven)\n    * [SBT](#sbt)\n    * [Gradle](#gradle)\n* [Usage](#usage)\n    * [General](#general)\n    * [Exception Handling Config](#exception-handling-config)\n    * [Value Handling Config](#value-handling-config)\n    * [Timing Config](#timing-config)\n    * [Backoff Strategy Config](#backoff-strategy-config)\n    * [Custom Backoff Strategies](#custom-backoff-strategies)\n    * [Simple Configs](#simple-configs)\n    * [CallExecutor](#callexecutor)\n    * [Call Status](#call-status)\n    * [Retry4jException](#retry4jexception)\n    * [Listeners](#listeners)\n    * [Async Support](#async-support)\n    * [Logging](#logging)\n* [Other Notes](#other-notes)\n      \n## Basic Code Examples\n\n### Handling Failures with Exceptions\n\n```java\nCallable\u003cObject\u003e callable = () -\u003e {\n    //code that you want to retry until success OR retries are exhausted OR an unexpected exception is thrown\n};\n\nRetryConfig config = new RetryConfigBuilder()\n        .retryOnSpecificExceptions(ConnectException.class)\n        .withMaxNumberOfTries(10)\n        .withDelayBetweenTries(30, ChronoUnit.SECONDS)\n        .withExponentialBackoff()\n        .build();\n        \ntry {  \n    Status\u003cObject\u003e status = new CallExecutorBuilder()\n        .config(config)\n        .build()\n        .execute(callable);\n    Object object = status.getResult(); //the result of the callable logic, if it returns one\n} catch(RetriesExhaustedException ree) {\n    //the call exhausted all tries without succeeding\n} catch(UnexpectedException ue) {\n    //the call threw an unexpected exception\n}\n```\n\nOr more simple using one of the predefined config options and not checking exceptions:\n\n```java\nCallable\u003cObject\u003e callable = () -\u003e {\n    //code that you want to retry\n};\n\nRetryConfig config = new RetryConfigBuilder()\n    .exponentialBackoff5Tries5Sec()\n    .build();\n\nStatus\u003cObject\u003e status = new CallExecutorBuilder().config(config).build().execute(callable);\n```\n\n### Handling All Results with Listeners\n\n```java\nCallable\u003cObject\u003e callable = () -\u003e {\n    //code that you want to retry\n};\n\nRetryConfig config = new RetryConfigBuilder()\n        .exponentialBackoff5Tries5Sec()\n        .build();\n\nCallExecutor executor = new CallExecutorBuilder\u003c\u003e()\n        .config(config).\n        .onSuccess(s -\u003e { //do something on success })\n        .onFailure(s -\u003e { //do something after all retries are exhausted })\n        .afterFailedTry(s -\u003e { //do something after a failed try })\n        .beforeNextTry(s -\u003e { //do something before the next try })\n        .onCompletion(s -\u003e { //do some cleanup })\n        .build();\n        \nexecutor.execute(callable);\n```\n\n## Dependencies\n\n### Maven\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.evanlennick\u003c/groupId\u003e\n    \u003cartifactId\u003eretry4j\u003c/artifactId\u003e\n    \u003cversion\u003e0.15.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### SBT\n\n```sbt\nlibraryDependencies += \"com.evanlennick\" % \"retry4j\" % \"0.15.0\"\n```\n### Gradle\n\n```groovy\ncompile \"com.evanlennick:retry4j:0.15.0\"\n```\n\n## Usage\n\n### General\n\nRetry4j does not require any external dependencies. It does require that you are using Java 8 or newer.\n\n* Javadocs are hosted at: http://www.javadoc.io/doc/com.evanlennick/retry4j/0.15.0.\n* Continuous integration results are available via Travis CI here: https://travis-ci.org/elennick/retry4j.\n* Code coverage information is available via Coveralls here: https://coveralls.io/github/elennick/retry4j.\n* Examples of Retry4j in use are documented here: https://github.com/elennick/retry4j-examples\n\n### Exception Handling Config\n\nIf you do not specify how exceptions should be handled or explicitly say **failOnAnyException()**, the CallExecutor will fail and throw an **UnexpectedException** when encountering exceptions while running. Use this configuration if you want the executor to cease its work when it runs into any exception at all.\n\n```java\nRetryConfig config = new RetryConfigBuilder()\n        .failOnAnyException()\n        .build();\n```\n\nIf you want to specify specific exceptions that should cause the executor to continue and retry on encountering, do so using the **retryOnSpecificExceptions()** config method. This method can accept any number of exceptions if there is more than one that should indicate the executor should continue retrying. All other unspecified exceptions will immediately interupt the executor and throw an **UnexpectedException**.\n\n```java\nRetryConfig config = new RetryConfigBuilder()\n        .retryOnSpecificExceptions(ConnectException.class, TimeoutException.class)\n        .build();\n```\n\nIf you want the executor to continue to retry on all encountered exceptions, specify this using the **retryOnAnyException()** config option.\n\n```java\nRetryConfig config = new RetryConfigBuilder()\n        .retryOnAnyException()\n        .build();\n```\n\nIf you want the executor to continue to retry when encountered exception's cause is among a list of exceptions, then specify **retryOnCausedBy()** config option\n```java\nRetryConfig config = new RetryConfigBuilder()\n        .retryOnCausedBy()\n        .retryOnSpecificExceptions(ConnectException.class, TimeoutException.class)\n        .build();\n```\n\nIf you want the executor to continue to retry on all encountered exceptions EXCEPT for a few specific ones, specify this using the **retryOnAnyExceptionExcluding()** config option. If this exception strategy is chosen, only the exceptions specified or their subclasses will interupt the executor and throw an **UnexpectedException**.\n\n```java\nRetryConfig config = new RetryConfigBuilder()\n        .retryOnAnyExceptionExcluding(CriticalFailure.class, DontRetryOnThis.class)\n        .build();\n```\n\nNOTE: When using `retryOnSpecificExceptions` and `retryOnAnyExceptionExcluding`, the call executor will also take into account if the encountered exceptions are subclasses of the types you specified. For example, if you tell the configuration to retry on any `IOException`, the executor will retry on a `FileNotFoundException` which is a subclass of `IOException`.\n\nIf you do not want to use these built-in mechanisms for retrying on exceptions, you can override them and create custom logic:\n\n```java\nRetryConfig config = new RetryConfigBuilder()\n        .retryOnCustomExceptionLogic(ex -\u003e {\n            //return true to retry, otherwise return false\n        })\n        .build();\n```\n\nIf you create custom exception logic, no other built-in retry-on-exception configuration can be used at the same time.\n\n### Value Handling Config\n\nIf you want the executor to retry based on the returned value from the Callable:\n\n```java\nRetryConfig config = retryConfigBuilder\n        .retryOnReturnValue(\"retry on this value!\")\n        .build();\n```\n\nThis can be used in combination with exception handling configuration like so:\n\n```java\nRetryConfig config = retryConfigBuilder\n        .retryOnSpecificExceptions(FileNotFoundException.class)\n        .retryOnReturnValue(\"retry on this value!\")\n        .build();\n```\n            \nIn the above scenario, the call execution will be considered a failure and a retry will be triggered if \n`FileNotFoundException.class` is thrown OR if the String `retry on this value!` is returned from the Callable logic.\n\nTo retry only using return values, you can disable exception retries using the `failOnAnyException()` configuration option:\n\n```java\nRetryConfig config = retryConfigBuilder\n        .failOnAnyException()\n        .retryOnReturnValue(\"retry on this value!\")\n        .build();\n```\n\n### Timing Config\n\nTo specify the maximum number of tries that should be attempted, specify an integer value in the config using the **withMaxNumberOfTries()** method. The executor will attempt to execute the call the number of times specified and if it does not succeed after all tries have been exhausted, it will throw a **RetriesExhaustedException**.\n\n\n```java\nRetryConfig config = new RetryConfigBuilder()\n        .withMaxNumberOfTries(5)\n        .build();\n``` \n\nIf you do not wish to have a maximum and want retries to continue indefinitely, instead us the `retryIndefinitely()` \noption:\n\n```java\nRetryConfig config = new RetryConfigBuilder()\n        .retryIndefinitely()\n        .build();\n````\n\nTo specify the delay in between each try, use the **withDelayBetweenTries()** config method. This method will accept a Java 8 Duration object or an integer combined with a ChronoUnit.\n\n```java\n//5 seconds\nRetryConfig config = new RetryConfigBuilder()\n        .withDelayBetweenTries(5, ChronoUnit.SECONDS)\n        .build();\n\n//2 minutes\nRetryConfig config = new RetryConfigBuilder()\n        .withDelayBetweenTries(Duration.of(2, ChronoUnit.MINUTES))\n        .build();\n\n//250 millis\nRetryConfig config = new RetryConfigBuilder()\n        .withDelayBetweenTries(Duration.ofMillis(250))\n        .build();\n```\n\n### Backoff Strategy Config\n\nRetry4j has built in support for several backoff strategies. They can be specified like so:\n\n```java\n//backoff strategy that delays with the same interval in between every try\nRetryConfig config = new RetryConfigBuilder()\n        .withFixedBackoff()\n        .build();\n\n//backoff strategy that delays at a slowing rate using an exponential approach\nRetryConfig config = new RetryConfigBuilder()\n        .withExponentialBackoff()\n        .build();\n\n//backoff strategy that delays at a slowing rate using fibonacci numbers\nRetryConfig config = new RetryConfigBuilder()\n        .withFibonacciBackoff()\n        .build();\n\n//backoff strategy that retries with no delay\n//NOTE: any value specified in the config for \"withDelayBetweenTries()\" will be ignored if you use this strategy\nRetryConfig config = new RetryConfigBuilder()\n        .withNoWaitBackoff()\n        .build();\n\n//backoff strategy that randomly multiplies the delay specified on each retry\n//useful if you want to force multiple threads not to retry at the same rate\nRetryConfig config = new RetryConfigBuilder()\n        .withRandomBackoff()\n        .build();\n\n//backoff strategy that delays at a slowing rate and also randomly multiplies the delay\n//combination of Exponential Backoff Strategy and Random Backoff Strategy\nRetryConfig config = new RetryConfigBuilder()\n        .withRandomExponentialBackoff()\n        .build();\n```\n\n### Custom Backoff Strategies\n\nCustom backoff strategies can be specified like so:\n\n```java\nRetryConfig config = new RetryConfigBuilder()\n        .withBackoffStrategy(new SomeCustomBackoffStrategy())\n        .build();\n```\n\n...where `SomeCustomBackoffStrategy` is an object that implements the `com.evanlennick.retry4j.backoff.BackoffStrategy` interface. The only mandatory method to implement is `getDurationToWait()` which determines how long to wait between each try. Optionally, the `validateConfig()` method can also be implemented if your backoff strategy needs to verify that the configuration being used is valid.\n\nFor examples creating backoff strategies, check out the provided implementations [here](https://github.com/elennick/retry4j/tree/master/src/main/java/com/evanlennick/retry4j/backoff).\n\n### Simple Configs\n\nRetry4j offers some predefined configurations if you just want to get rolling and worry about tweaking later. These configs can be utilized like so:\n\n```java\nnew RetryConfigBuilder()\n    .fixedBackoff5Tries10Sec()\n    .build();\n\nnew RetryConfigBuilder()\n    .exponentialBackoff5Tries5Sec()\n    .build();\n\nnew RetryConfigBuilder()\n    .fiboBackoff7Tries5Sec()\n    .build();\n\nnew RetryConfigBuilder()\n    .randomExpBackoff10Tries60Sec()\n    .build();\n```\n\n### CallExecutor\n\nExecuting your code with retry logic is as simple as building a **CallExecutor** using **CallExecutorBuilder** with your configuration and then calling execute:\n\n```java\nnew CallExecutorBuilder.config(config).build().execute(callable);\n``` \n\nThe CallExecutor expects that your logic is wrapped in a **java.util.concurrent.Callable**.\n\n### Call Status\n\nAfter the executor successfully completes or throws a RetriesExhaustedException, a **Status** object will returned or included in the exception. This object will contain detailed information about the call execution including the number of total tries, the total elapsed time and whether or not the execution was considered successful upon completion.\n\n```java\nStatus status = new CallExecutorBuilder().config(config).build().execute(callable);\nSystem.out.println(status.getResult()); //this will be populated if your callable returns a value\nSystem.out.println(status.wasSuccessful());\nSystem.out.println(status.getCallName());\nSystem.out.println(status.getTotalDurationElapsed());\nSystem.out.println(status.getTotalTries());\nSystem.out.println(status.getLastExceptionThatCausedRetry());\n```  \n\nor\n\n```java\n    try {  \n        new CallExecutorBuilder().config(config).build().execute(callable);\n    } catch(RetriesExhaustedException cfe) {\n        Status status = cfe.getStatus();\n        System.out.println(status.wasSuccessful());\n        System.out.println(status.getCallName());\n        System.out.println(status.getTotalDurationElapsed());\n        System.out.println(status.getTotalTries());\n        System.out.println(status.getLastExceptionThatCausedRetry());\n    }\n```\n\n### Retry4jException\n\nRetry4j has the potential throw several unique exceptions when building a config, when executing retries or upon completing execution (if unsuccessful). All Retry4j exceptions are unchecked. You do not have to explicitly catch them if you wish to let them bubble up cleanly to some other exception handling mechanism. The types of **Retry4jException**'s are:\n\n* **UnexpectedException** - Occurs when an exception is thrown from the callable code. Only happens if the exception thrown was not one specified by the *retryOnSpecificExceptions()* method in the config or if the *retryOnAnyException()* option was not specified as part of the config.\n* **RetriesExhaustedException** - This indicates the callable code was retried the maximum number of times specified in the config via *withMaxNumberOfTries()* and failed all tries.\n* **InvalidRetryConfigException** - This exception is thrown when the RetryConfigBuilder detects that the invoker attempted to build an invalid config object. This will come with a specific error message indicating the problem. Common issues might be trying to specify more than one backoff strategy (or specifying none), specifying more than one exceptions strategy or forgetting to specify something mandatory such as the maximum number of tries.\n\n***NOTE:*** Validation on the **RetryConfigBuilder** can be disabled to prevent **InvalidRetryConfigException**'s from ever being thrown. This is not recommended in application code but may be useful when writing test code. Examples of how to disable it:\n\n```java\nnew RetryConfigBuilder().setValidationEnabled(false).build()\n```\n\nor\n\n```java\nnew RetryConfigBuilder(false);\n```\n\n### Listeners\n\nListeners are offered in case you want to be able to add logic that will execute immediately after a failed try or immediately before the next retry (for example, you may want to log or output a statement when something is retrying). These listeners can be specified like so:\n\n```java\nCallExecutor executor = new CallExecutorBuilder().config(config).build();\n\nexecutor.afterFailedTry(s -\u003e { \n    //whatever logic you want to execute immediately after each failed try\n});\n\nexecutor.beforeNextTry(s -\u003e {\n    //whatever logic you want to execute immediately before each try\n});\n```\n\nTwo additional listeners are also offered to indicate when a series of retries has succeeded or failed. They can be specified like so:\n\n```java\nexecutor.onSuccess(s -\u003e {\n    //whatever logic you want to execute when the callable finishes successfully\n});\n\nexecutor.onFailure(s -\u003e {\n    //whatever logic you want to execute when all retries are exhausted\n});\n```\n\n***NOTE:*** If you register a failure listener with the CallExecutor, it will toggle off the throwing of \n**RetriesExhaustedException**'s. Handling a failure after retries are exhausted will be left up to the listener.\n\nIf you wish to execute any sort of cleanup or finalization logic that will execute no matter what the final results is (success, exhausted retries, unexpected exception throw) you can implement the following listener:\n\n\n```java\nexecutor.onCompletion(s -\u003e {\n    //whatever logic you want to execute after the executor has completed, regardless of status\n});\n```\n\nListeners can be chained together:\n\n```java\nnew CallExecutorBuilder\u003c\u003e()\n       .config(config)\n       .onSuccess(s -\u003e System.out.println(\"Success!\"))\n       .onCompletion(s -\u003e System.out.println(\"Retry execution complete!\"))\n       .onFailure(s -\u003e System.out.println(\"Failed! All retries exhausted...\"))\n       .afterFailedTry(s -\u003e System.out.println(\"Try failed! Will try again in 0ms.\"))\n       .beforeNextTry(s -\u003e System.out.println(\"Trying again...\"))\n       .build()\n       .execute(callable);\n```\n\n### Async Support\n\nRetry4j has some built in support for executing and retrying on one or more threads in an asynchronous fashion. The \n`AsyncCallExecutor` utilizes threading and async mechanisms via Java's `CompletableFuture` API. A basic example of this \nin action with a single call:\n\n```java\nAsyncCallExecutor\u003cBoolean\u003e executor = new CallExecutorBuilder().config(config).buildAsync();\nCompletableFuture\u003cStatus\u003cBoolean\u003e\u003e future = executor.execute(callable);\nStatus\u003cBoolean\u003e status = future.get();\n```\n\nIn the above case, the logic in the callable will begin executing immediately upon `executor.execute(callable)` being \ncalled. However, the callable (with retries) will execute on another thread and the original thread that started \nexecution will not be blocked until `future.get()` is called (if it hasn't completed).\n    \nThis executor can also be used to trigger several Callable's in parallel:\n\n```java\nAsyncCallExecutor\u003cBoolean\u003e executor = new CallExecutorBuilder().config(retryOnAnyExceptionConfig).buildAsync();\n\nCompletableFuture\u003cStatus\u003cBoolean\u003e\u003e future1 = executor.execute(callable1);\nCompletableFuture\u003cStatus\u003cBoolean\u003e\u003e future2 = executor.execute(callable2);\nCompletableFuture\u003cStatus\u003cBoolean\u003e\u003e future3 = executor.execute(callable3);\n\nCompletableFuture.allOf(future1, future2, future3).join();\n```\n\nIf you wish to define a thread pool to be used by your `AsyncCallExecutor`, you can define and pass in an \n`ExecutorService` in the constructor. When using this pattern, it's important to remember that this thread pool will not\nshut itself down and you will have to explicitly call `shutdown()` on the `ExecutorService` if you want it to be cleaned\nup.\n\n```java\nExecutorService executorService = Executors.newFixedThreadPool(10);\nnew CallExecutorBuilder().config(config).buildAsync(executorService);\n```\n\nYou can register retry listeners and configuration on an `AsyncCallExecutor` in the same fashion as the normal, \nsynchronous `CallExecutor`. All calls in all threads that are triggered from an `AsyncCallExecutor` after its \nconstruction will use the same listeners and configuration.\n\n### Logging\n\nRetry4j contains detailed internal logging using [SLF4J](https://www.slf4j.org/manual.html). If you do not specify a SLF4J implementation, these logs will be discarded. If you do provide an implementation (eg: Logback, Log4J, etc) you can specify the log level on the `com.evanlennick.retry4j` package to set Retry4j logging to a specific level.\n\n## Other Notes\n\nRetry4j follows semantic versioning: http://semver.org/. As it is still version 0.x.x and prior to 1.0.0, the API is subject to rapid change and breakage.\n\nThere are a number of other retry libraries for Java and the JVM that might better suit your needs. Please feel free to check out the following libraries as well if Retry4j doesn't fit:\n\n* Guava Retrying - https://github.com/rhuffman/re-retrying\n* Failsafe - https://github.com/jhalterman/failsafe\n* Spring Retry - https://github.com/spring-projects/spring-retry\n* resilience4j - https://github.com/resilience4j/resilience4j\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felennick%2Fretry4j","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felennick%2Fretry4j","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felennick%2Fretry4j/lists"}