FS2 is a pull-based stream library for Scala that treats demand as the unit of work
Compositional, streaming I/O library for Scala
At a glance
- What is it?
- Typelevel's functional streaming library builds on Cats and Cats-Effect, publishes for four Scala versions plus Scala.js and Native, and has spent recent releases refining backpressure semantics.
- Who is it for?
- FS2 is the right fit when your Scala code needs to describe a pipeline over an effect type that can change, or when you need the same stream program to run on the JVM and on Scala Native, since the core types are polymorphic in the effect and the library publishes for Scala 2.12, 2.13, Scala 3, Scala.js and Scala Native.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 9 days ago.
- What is it written in?
- Mainly Scala, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 7, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The name is an acronym, and the design goals are four words
The README states the design goals directly: compositionality, expressiveness, resource safety and speed. That ordering is a fair summary of what the library is for. A stream library that is not compositional is a pipeline builder, and a stream library that is not resource safe leaks file handles and sockets in exactly the places where you can least afford it.
The name is explained in the same paragraph, which is the sort of detail that tells you the project has been around long enough to have a history. FS2 is a modified acronym for Functional Streams for Scala, abbreviated FSS and then FS2. The doubling is not vanity; it is what happens when a project's original name was already taken.
GitHub describes the repository as a compositional, streaming I/O library for Scala, with topics covering functional programming, streams and streaming computation. The README adds the word effectful to the formal description, which is the important qualifier. FS2 does not stream values, it streams effects that produce values, and everything about its ergonomics follows from that.
Four Scala versions, two compile targets, one set of core types
The platform matrix is the first thing to check, and it is unusually broad. FS2 is available for Scala 2.12, Scala 2.13, Scala 3, Scala.js and Scala Native. The Maven Central badge in the README points at the `co.fs2` organization and the `fs2-core_2.13` artifact, which tells you the naming convention to expect in a build file: a cross-built core module suffixed with the binary version.
The repository layout separates those concerns into directories you can read in the tree:
core/
io/
protocols/
reactive-streams/`core/` is where the stream and pull types live, `io/` is the effectful layer with files, sockets and processes, `protocols/` holds the interchange formats, and `reactive-streams/` is the bridge to the Reactive Streams interface. There are also `scodec/`, `benchmark/`, `integration/`, `testdata/`, `site/` and `docs/`.
The `io/` directory existing at all is the practical signal. A pure `Stream[IO, Byte]` is an abstraction; a library that also ships the filesystem, TCP, UDP, Unix socket and subprocess implementations is a different commitment. Release 3.13.0 unified the TCP, UDP and Unix socket APIs and added a Cats-Effect selector-based DatagramSocket implementation for the JVM, which is the kind of consolidation that only makes sense in a library that owns that layer.
Polymorphic in the effect, which is the design decision to argue about
FS2 is built on Cats and Cats-Effect. The README then makes a claim that deserves to be read twice: regardless of those dependencies, the FS2 core types, streams and pulls, are polymorphic in the effect type, as long as it is compatible with Cats-Effect typeclasses, and FS2 can therefore be used with other effect libraries such as Monix.
This is the part that separates FS2 from a library that just requires Cats-Effect. Because a `Stream` is described by its effect parameter rather than fixed to `IO`, the same stream program can be run under different runtimes, and the test suite for a stream does not have to commit to a production effect type. The cost is real and the README does not pretend otherwise: the constraint is compatibility with Cats-Effect typeclasses, so the effect library has to cooperate with that hierarchy. That is a narrower door than pure typeclass polymorphism, but a much wider one than depending on `IO` alone.
The alternative framing matters too. Libraries built on an actor runtime let you write a handler and receive messages, which is comfortable when your work is naturally concurrent and message-driven. FS2 is built the other way round, around a pull model where a consumer signals demand and a producer supplies what was asked for. Neither is better in general. The pull model composes better and backpressures by construction; the actor model fits workloads whose concurrency is the point.
What recent releases actually changed
Version 3.14.0 shipped on 2026-09-05, and reading its notes is the fastest way to understand where this library spends its effort. Almost none of it is new surface area.
Two features: support for `readOutputStream` on Scala Native, and support for process stream redirection and error stream merging. Everything else is a fix. There is a fix to avoid an NPE when a relative path is given to `Watcher::watch`, and a fix for a `linesLimited` bypass when a line completes within a single chunk, which is the kind of bug that only shows up on an input boundary and would be miserable to track down from a report. There is a fix for the `SelectingIpDatagramSocket` option and close handling, and one for an intermittent timeout in `parevalmap` by ensuring semaphore release.
Two of those fixes describe backpressure directly. One is described as merging by pulling from upstream only when downstream has demand. Another is a documentation change recording the scope limitations of the concurrent stream combinators. Both are admissions that demand semantics in a concurrent stream are subtle enough to need a fix and a documented caveat in the same release.
Version 3.13.0 in March 2026 brought Cats Effect 3.7.0 and Scala Native 0.5 with real multi-threading, which is a much bigger deal than the version number suggests. Without real threads, a streaming library on Native cannot do the concurrent work it does on the JVM. It also added virtual threads for `JdkUnixSockets`, a `Socket#sendFile` implementation, support for collecting elements into a `SortedSet` or `SortedMap`, and instances for lifting kinds and values into a `Stream`. That release also fixed a spin-loop bug in `TLSEngine` and close soundness holes arising from Scala 3.8.0.
Where the documentation lives, and what the README does not tell you
The README is a signpost, and it is honest about being one. Everything substantive is one click away: Scaladoc API documentation, an official guide on fs2.io that it recommends as the starting point, a documentation page that indexes conference talks, academic papers and blog posts about the implementation, and an FAQ page the project invites you to contribute to through issues and pull requests.
Support runs through the Typelevel Discord channel and Stack Overflow under the tag FS2, and the README says Discord will generally get you a quicker answer. That is worth knowing before you pick a library: the question you will actually have is usually answered by the Scala community rather than by the project, because the answer often depends on which effect runtime you picked.
There is an ecosystem page listing libraries and data store integrations built on FS2, and the project asks to be added to it by pull request or message. That page is the closest thing to a compatibility list, and for a stream library it matters more than a star count: whether the Kafka client or the database layer you need already has an FS2 integration will decide the project before the API does.
One detail in the README is a small maintenance signal. YourKit is thanked by name for supporting ongoing performance tuning efforts with licences for their product, and there is a `benchmark/` directory in the tree. A library that accepts profiling tooling for its performance work is a library where throughput was treated as a goal rather than an afterthought.
The organization moved, and GitHub reports no licence identifier
Two repository facts are worth stating plainly because they will confuse anyone arriving from a search result. First, GitHub reports the repository as `typelevel/fs2`, while several links inside the README still point at `functional-streams-for-scala/fs2`, including the continuous integration badge and the code of conduct link. The project moved into the Typelevel organization and the old paths redirect, which is normal for a moved repository. Use the Typelevel path for anything you script.
Second, GitHub reports the licence field as NOASSERTION, which is GitHub's way of saying it could not classify the licence automatically, not that the project has no licence. There is a `LICENSE` file at the root of the tree. Both facts are in the repository at once and neither contradicts the other: the file exists, and the automated detector did not recognise it. If licence terms matter to you, read `LICENSE` in the repository rather than relying on the badge GitHub renders, and treat the classification gap as a reason to check rather than a reason to assume.
The default branch is `main`, the last push was on 2026-09-28, and the repository is not archived, so the two most recent release tags and the branch head are consistent with each other.
Editorial conclusion
FS2 is the right fit when your Scala code needs to describe a pipeline over an effect type that can change, or when you need the same stream program to run on the JVM and on Scala Native, since the core types are polymorphic in the effect and the library publishes for Scala 2.12, 2.13, Scala 3, Scala.js and Scala Native. It is the wrong choice for a team that wants imperative control flow with a thread pool, because the pull model asks you to reason about demand rather than loops. Version 3.14.0 shipped on 2026-09-05 and its release notes are mostly correctness work around demand and resource release, which is the honest signal about where this library's difficulty sits. Start at fs2.io for the official guide, then read the release notes for the concurrent stream combinators, whose documented scope limitations are the part most likely to surprise you.
Frequently asked questions
What is FS2 and what is it used for?
FS2 is a purely functional, effectful and polymorphic stream processing library for Scala, with design goals of compositionality, expressiveness, resource safety and speed. Streams are described over an effect type rather than a concrete one, so the same program can run under different effect runtimes.
Which Scala versions and platforms does FS2 support?
The README lists Scala 2.12, Scala 2.13, Scala 3, Scala.js and Scala Native. Release 3.13.0 added Scala Native 0.5 support with real multi-threading, and 3.14.0 added `readOutputStream` on Scala Native.
Does FS2 depend on Cats Effect, or can I use another effect library?
FS2 is built on Cats and Cats-Effect, but the core stream and pull types are polymorphic in the effect type, so it can be used with other effect libraries such as Monix. The README's constraint is that the effect must be compatible with Cats-Effect typeclasses.
Is FS2 actively maintained?
Version 3.14.0 was published on 2026-09-05 and GitHub reports the last push on 2026-09-28. Recent work is mostly correctness fixes around backpressure and resource release, including a change described as pulling from upstream only when downstream has demand.
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/typelevel-fs2)