{"id":16209562,"url":"https://github.com/dan-oak/java-concurrent-polling","last_synced_at":"2025-07-24T23:34:44.717Z","repository":{"id":95581422,"uuid":"177334753","full_name":"dan-oak/java-concurrent-polling","owner":"dan-oak","description":"Examples of Java asynchronous pollers using `java.concurrent` classes","archived":false,"fork":false,"pushed_at":"2019-10-19T00:59:40.000Z","size":98,"stargazers_count":5,"open_issues_count":0,"forks_count":5,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-07-23T23:42:16.394Z","etag":null,"topics":["java","java-concurrency","java-concurrent","java-multithreading","java-threads","multithreading"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dan-oak.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2019-03-23T20:04:05.000Z","updated_at":"2025-06-30T09:39:58.000Z","dependencies_parsed_at":"2023-05-20T23:00:09.515Z","dependency_job_id":null,"html_url":"https://github.com/dan-oak/java-concurrent-polling","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dan-oak/java-concurrent-polling","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dan-oak%2Fjava-concurrent-polling","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dan-oak%2Fjava-concurrent-polling/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dan-oak%2Fjava-concurrent-polling/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dan-oak%2Fjava-concurrent-polling/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dan-oak","download_url":"https://codeload.github.com/dan-oak/java-concurrent-polling/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dan-oak%2Fjava-concurrent-polling/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266922911,"owners_count":24006983,"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-07-24T02:00:09.469Z","response_time":99,"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":["java","java-concurrency","java-concurrent","java-multithreading","java-threads","multithreading"],"created_at":"2024-10-10T10:30:14.172Z","updated_at":"2025-07-24T23:34:44.699Z","avatar_url":"https://github.com/dan-oak.png","language":"Java","readme":"# Concurrent Polling in Java\n\nOnce I wondered: \n\n_What's the best way to poll in an asynchronous Java application?_\n\n## Intention\n\nThis article is intended to provide practical usage examples of `java.concurrent` classes by building different \nasynchronous pollers. Fully working examples and many variations can be found [in the repository][repo].\n\nIt might be useful to you if you are learning tools in Java concurrency or just need some simple and efficient\npoller implementation. \n\nI worked on a service responsible for Transfer State Machine in [TransferWise][tw], one of the central services \nwhich managed the integrity of the Transfer data, was defining all possible State Transitions and maintained strict \norder of events processing. All Transfer actions there went through asynchronous channels, which are an interesting \ntopic alone, but for now I'll show you a way to conveniently test asynchronous flows by polling.        \n\n## Tests\n  \nPolling is best suited for integration tests. We have been launching an application, sending some events, commands or \nmessages to the async channels and waiting for a data change in a database. On a toy example, something like:\n \n```groovy\ndef \"eventually might generate something divisible by 4\"() {\n    given:\n        def repo = new ListRepository\u003cInteger\u003e()\n        def rng = new RandomIntegerPersister(repo)\n        rng.start(1, 1, SECONDS)                     // Spawn our worker thread \n\n    expect:\n        pollForIsDivisibleBy(repo, 4)                // Spawn our polling thread\n            .map({ log.info(\"Found: {}\", it); it })  // Log some info about found object when needed\n            .isPresent()                             // And make the test assertion itself \n}\n\ndef \u003cT\u003e Optional\u003cT\u003e pollForIsDivisibleBy(Repository\u003cT\u003e repo, T n) throws InterruptedException {\n    // Poll every 1 second with timeout 5 seconds:\n    return poller.poll({ repo.findFirst({ it % n == 0 }) }, 1, SECONDS, 5, SECONDS)\n}\n```\n\n## True or False\n\nNow when you see how the test might look like in the end, here is how we can implement and use the most naive version\nof a simple poller which returns only a boolean result:\n\n```groovy\ngiven:\n    // ...    \n    def pollee = {\n        repo.findFirst({ it % 4 == 0 })\n            .map({ log.info(\"Found: {}\", it); it })\n            .isPresent()\n    }\nexpect:\n    poller.poll(pollee, 1, SECONDS, 5, SECONDS)\n```\n\n```java\npublic class BooleanThreadPoller {\n    public Boolean poll(Callable\u003cBoolean\u003e pollee,\n                        long period, TimeUnit periodTimeUnit,\n                        long timeout, TimeUnit timeoutTimeUnit\n    ) {\n        AtomicReference\u003cBoolean\u003e resultRef = new AtomicReference\u003c\u003e();\n        Thread poller = new Thread(() -\u003e {\n            try {\n                long startMs = System.currentTimeMillis();\n                long timeoutMs = TimeUnit.MILLISECONDS.convert(timeout, timeoutTimeUnit);\n                while (true) {\n                    if (System.currentTimeMillis() - startMs \u003e timeoutMs) {\n                        return;                                                             // Timed out\n                    }\n                    if (pollee.call()) {\n                        resultRef.set(true);\n                        return;                                                             // Success\n                    } else {\n                        periodTimeUnit.sleep(period);                                       // Waiting\n                    }\n                }\n            } catch (Exception e) { }\n        });\n        poller.start();\n        try { poller.join(); } catch (InterruptedException e) { }                           // Waiting\n        return resultRef.get();\n    }\n}\n```\n\nThe first thing to notice here is `AtomicReference` which is used as an _effectively `final`_ wrapper \naround the result. Without supplying a final or effectively final variable to lambda Java does not compile.\n\nAlso there is a significant difference between `new Thread()` `.start()`/`.run()`. `Thread` implements `Runnable` \nbut `run` does not actually start execution in a new thread as one might expect, instead it just executes in \na current thread, but `start` in a new one.\n\n## Schedulers\n\nNow we can sacrifice a bit of flexibility for the sake of a more common approach, using `ScheduledThreadPoolExecutor`: \n\n```java\npublic class Poller\u003cAnswer\u003e {\n\n    public Optional\u003cAnswer\u003e poll(Callable\u003cOptional\u003cAnswer\u003e\u003e question,\n                                 long period, TimeUnit periodTimeUnit,\n                                 long timeout, TimeUnit timeoutTimeUnit\n    ) throws InterruptedException {\n        SynchronousQueue\u003cOptional\u003cAnswer\u003e\u003e answerQueue = new SynchronousQueue\u003c\u003e();\n        Runnable questionRunnable = () -\u003e {\n            try {\n                answerQueue.put(question.call());\n            } catch (Exception e) {\n                log.error(\"Polling error\", e);\n            }\n        };\n        ScheduledFuture\u003c?\u003e scheduledFuture = Executors.newSingleThreadScheduledExecutor()\n            .scheduleAtFixedRate(questionRunnable, 0, period, periodTimeUnit);\n        log.info(\"Started with period of {} {}\", period, periodTimeUnit);\n        Optional\u003cAnswer\u003e answer = answerQueue.poll(timeout, timeoutTimeUnit);\n        scheduledFuture.cancel(false);\n        return answer;\n    }\n\n}\n```\n\nThe only problem is that the scheduled task never stops. To solve it we just use `SynchronousQueue` to execute and \nset results from the scheduler, and wait for them on the main thread. When we got a result or timed out, the task is\nstopped.\n\nThat's it for now.\n\nRegards,  \nDan\n\n## References\n\nWhen I was looking for examples, I've found many notes about `BlockingQueue` but no one actually showed \nanything specific, it motivated me to write this, in the process of which I learned more and discovered that\nwe don't even need any implementations of `BlockingQueue`, although the version with `SynchronousQueue` is \nquite clean and efficient.\n\nThanks to the authors of the following posts:\n\n- \"Implement responsive Java polling: https://blog.codecentric.de/en/2018/09/implement-responsive-java-polling/\n- CountDownLatch: https://stackoverflow.com/questions/17827022/how-is-countdownlatch-used-in-java-multithreading\n- `new Thread()` `.start()`/`.run()`: https://javarevisited.blogspot.com/2012/03/difference-between-start-and-run-method.html\n- `Thread.sleep`/`TimeUnit.sleep`: https://stackoverflow.com/questions/9587673/thread-sleep-vs-timeunit-seconds-sleep\n- `synchronized`: https://www.baeldung.com/java-synchronized\n- Java Queue implementations: https://docs.oracle.com/javase/tutorial/collections/implementations/queue.html\n- `SynchronousQueue`: https://www.youtube.com/watch?v=QCMt324j64U\n- Effectively final: https://www.baeldung.com/java-lambda-effectively-final-local-variables\n- Stop execution of a ScheduledFuture at some point: https://stackoverflow.com/a/7376085/2601742 \n\nOne of the popular polling libraries: [Awaitility][await]. It works well with JUnit and Hamcrest matchers, but at the \ntime of writing it lacks functionality just to poll and get the result as a return value.\n\n[repo]: https://github.com/danylo-dubinin/java-concurrent-polling/\n[tw]: https://transferwise.com\n[await]: https://github.com/awaitility/awaitility/\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdan-oak%2Fjava-concurrent-polling","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdan-oak%2Fjava-concurrent-polling","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdan-oak%2Fjava-concurrent-polling/lists"}