core.async: Clojure channels and go blocks for async communication
Facilities for async programming and communication in Clojure
At a glance
- What is it?
- core.async provides CSP-style channels and go blocks for concurrent Clojure and ClojureScript programs. Version 1.9.865 is the current stable release; an alpha flow sub-library is in the 1.10.x branch with all APIs subject to change.
- Who is it for?
- core.async suits Clojure applications that coordinate concurrent work through channels and go blocks, avoiding callback nesting while keeping control flow readable. Use version 1.9.865 for stable code.
- Can I use it commercially?
- Yes, with conditions. EPL-1.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 122 days ago.
- What is it written in?
- Mainly Clojure, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Channels and go blocks replace callbacks as the coordination model
core.async brings Communicating Sequential Processes (CSP) style concurrency to Clojure. Instead of chaining callbacks or composing futures, channels act as queues through which values pass between concurrent processes. A go block runs on a thread pool and can park, rather than block, while waiting for a value on a channel, freeing the thread for other work during the wait.
Clojure's own future and delay forms provide concurrency primitives but do not support parking semantics. When a future blocks on I/O, it holds a thread for the full duration. go blocks in core.async do not: the thread is released at a park point and the block resumes on a free thread when a value arrives. This distinction is what makes core.async useful for coordinating many concurrent lightweight processes on a small thread pool.
For applications that need to coordinate work across independent processes without callback chains, channels are the communication primitive: one process puts a value, another takes it, and the channel buffers or parks depending on its capacity. Rich Hickey's talk on core.async, linked in the README, explains the design rationale behind choosing CSP over alternatives.
Adding core.async with deps.edn, Leiningen, or Maven
For a deps.edn project, add this to the :deps map:
org.clojure/core.async {:mvn/version "1.9.865"}For a Leiningen project, add the following to the :dependencies vector in project.clj:
[org.clojure/core.async "1.9.865"]For Maven, the coordinates are groupId org.clojure, artifactId core.async, version 1.9.865:
<dependency>
<groupId>org.clojure</groupId>
<artifactId>core.async</artifactId>
<version>1.9.865</version>
</dependency>All released versions are indexed on Maven Central. Once the dependency is in place, the walkthrough.clj file in examples/ covers the core primitives. It is linked from the README as the code walkthrough and is the recommended entry point.
core.async follows a MAJOR.MINOR.COMMITS versioning scheme, not semantic versioning. The README is explicit: MAJOR and MINOR give a relative indication of change size, but COMMITS is a monotonically increasing counter of commits since the beginning of the repository. Version 1.9.865 means this was the 865th commit overall.
The go macro expands to a JVM state machine at compile time, not at runtime
What makes go blocks unusual in the JVM world is that they are a compile-time transformation. The go macro expands its body into a state machine that can suspend and resume at channel operations without holding a thread. This expansion previously lived in the ioc-macros namespace. With the ASYNC-252 change in 1.7.701, it moved to the impl.go namespace.
Tim Baldridge's two-part talk on go macro internals, linked in the README, explains how that expansion works. Part 1 and Part 2 are separate from the 2013 Clojure/conj talk, which covers usage at a higher level and is also linked in the README along with its example code.
One practical boundary follows from compile-time expansion: the go macro cannot wrap code that escapes its scope and still provide parking semantics for that outer code. Operations on channels inside a go block park; code called outside the go block does not share that guarantee. This is why io-thread exists as a separate primitive: operations that genuinely block on I/O should hold a real thread rather than mislead the go block into parking when blocking is the actual behavior.
io-thread and clojure.core.async.executor-factory replaced clojure.core.async.pool-size in 1.8.730
Before 1.8.730, the go block thread pool was controlled by the `clojure.core.async.pool-size` system property. Release 1.8.730 removed that property (ASYNC-260) and introduced the io-thread macro and a new `clojure.core.async.executor-factory` system property for customizing the executor. Code that set `clojure.core.async.pool-size` at startup will see no error on 1.8.730 and later, but the property will have no effect.
Release 1.9.865 went further by switching io-thread to use JVM virtual threads (ASYNC-272). The changelog does not specify what io-thread falls back to on JVMs that do not support virtual threads. The same release changed noop callbacks to dispatch on the caller thread (ASYNC-269) and changed alts!! callbacks to dispatch on the caller when possible (ASYNC-273). Both of those alter which thread callback code runs on, which can change execution ordering for applications that depend on it.
The system property `clojure.core.async.go-checking` was also updated in the 1.8.x beta series to work with the new executor pools, as noted in ASYNC-259.
core.async.flow is in alpha: futurize and broadcast are not yet stable
A sub-library called core.async.flow has been in the alpha series since 1.9.808-alpha1 (2025-04-28). Its APIs are subject to change, and the changelog repeats that warning for 1.10.864-alpha1 (2026-03-19). The 1.10.x branch carries the most recent flow work.
The flow API includes a `futurize` function that wraps a computation to return a future. Two releases addressed bugs in it: 1.10.864-alpha1 fixed futurize returning nothing when an Executor changed (ASYNC-274), and 1.10.870-alpha2 (2026-04-06) fixed futurize not propagating exceptions (ASYNC-275). A broadcast system was added in 1.9.847-alpha3 as a first cut.
One detail in the alpha changelog signals active design work rather than just surface additions: ASYNC-262 was added in 1.9.829-alpha2 (use virtual threads for go and io-thread when available) and then reverted in 1.9.847-alpha3. The reason for the revert is not given in the changelog. Applications that need flow should wait for a stable non-alpha release before relying on it.
ASYNC-234 and ASYNC-255: two bugs in alts that affected memory and nil handling
ASYNC-234, fixed in 1.8.741 (2025-04-07), found that inactive alt handlers were holding strong references to caller state after an alt operation completed. In practice, this meant that alts called in a loop with large data structures could prevent garbage collection of that data even after the alt chose a different branch. Code that calls alts or alts!! in loops where each iteration processes a new large value was the case most affected by this leak.
ASYNC-255, included in 1.8.730, added a guard in the alts function against putting a nil message onto a channel. nil in core.async signals a closed channel. Before this fix, a nil put could be indistinguishable from a channel close at the receiving end. The guard rejects nil puts at entry rather than letting the ambiguity propagate downstream.
Developers upgrading from versions before 1.8.741 to 1.8.741 or later should review any code using alts or alts!! in loops with large values, since memory reclamation behavior changes at that boundary.
No GitHub issues or PRs: contributions go to JIRA and require a signed CA
core.async does not accept GitHub pull requests or issues. Bug reports and enhancement requests go to the JIRA project at clojure.atlassian.net/browse/ASYNC. Contributing code requires a signed Contributor Agreement, per the policy at clojure.org/community/contributing.
Running the ClojureScript tests requires Leiningen. The process has three steps: run `lein cljsbuild once`, open script/runtests.html in a browser, then check the JavaScript console for test results. No equivalent CLI command for Clojure-only tests appears in the README. CI status for ClojureScript tests is published via the .github/ workflow badge in the README.
Release 1.9.865 added datafy support for channels and buffers. datafy is part of Clojure's clojure.datafy namespace and allows tooling and REPL environments to inspect a channel as data rather than as an opaque object. Before this change, a channel appeared as an opaque reference in the REPL. With datafy support, the buffer contents and state are inspectable as a map, which is useful in concurrent programs where channel state is otherwise hard to observe without reaching into internals.
The last push to the repository was on 2026-06-12. License is EPL-1.0, the same license used by Clojure itself. EPL-1.0 is a copyleft license with implications for distribution of modifications, and the LICENSE file at the repository root holds the full text.
Editorial conclusion
core.async suits Clojure applications that coordinate concurrent work through channels and go blocks, avoiding callback nesting while keeping control flow readable. Use version 1.9.865 for stable code. Skip the 1.10.x alpha branch unless you are prepared to track API changes in core.async.flow. Before upgrading from any version before 1.8.741, review code that uses alts or alts!! in loops with large data values, because the ASYNC-234 fix changes how memory is reclaimed in those loops. Bugs go to JIRA at clojure.atlassian.net/browse/ASYNC, not to GitHub.
Frequently asked questions
How do I add core.async to a Clojure project?
For deps.edn, add org.clojure/core.async {:mvn/version "1.9.865"} to your :deps map. For Leiningen, add [org.clojure/core.async "1.9.865"] to :dependencies. Maven coordinates are groupId org.clojure, artifactId core.async, version 1.9.865.
Is core.async.flow stable?
No. core.async.flow has been in alpha since 1.9.808-alpha1 (2025-04-28), and the changelog for 1.10.864-alpha1 repeats that all its APIs are subject to change. The 1.10.x branch holds the most recent flow work.
How do I report a bug in core.async?
Submit reports to the JIRA project at clojure.atlassian.net/browse/ASYNC. GitHub pull requests and issues are not accepted. Contributing code requires a signed Contributor Agreement as described at clojure.org/community/contributing.
What does the version number in core.async mean?
core.async uses MAJOR.MINOR.COMMITS versioning, not semantic versioning. The README says MAJOR and MINOR give a relative indication of change size, and COMMITS is an ever-increasing counter of commits from the start of the repository.
Does core.async work with ClojureScript?
Yes. ClojureScript support is included, and the CI workflow badge in the README tracks ClojureScript test status. Tests run with lein cljsbuild once, then view results in the JavaScript console after opening script/runtests.html.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/clojure-core-async)