fs2-nats

A functional, streaming NATS client for Scala 3 — built on Cats Effect 3 and FS2.

Every subscription is a Stream. Every connection is a Resource. Nothing happens until you ask it to.

fs2-nats lets you talk to NATS the functional way. Subscriptions are native fs2.Streams, the client is a Resource that cleans up after itself, reconnection and backpressure are built in, and the whole surface — core pub/sub, request/reply, JetStream, Key-Value, and Object Store — stays inside Cats Effect from the first byte to the last. No callbacks, no hidden threads, no surprises.

Highlights

Compatibility

Version
Scala 3.3.7 (Scala 3.3 LTS)
JDK 11, 17, 21, 25 — minimum 11
Cats Effect 3.7.x
FS2 3.13.x
NATS Server 2.9+ recommended

Core messaging, headers, and JetStream work against NATS Server 2.2+. The Key-Value and Object Store features use the JetStream Direct Get API, which requires NATS Server 2.9+.

Installation

sbt

libraryDependencies += "de.thatscalaguy" %% "fs2-nats" % "0.2.0"

Mill

ivy"de.thatscalaguy::fs2-nats:0.2.0"

scala-cli

//> using dep de.thatscalaguy::fs2-nats:0.2.0

Hello, NATS

Start a server with docker run -p 4222:4222 nats:latest, then:

import cats.effect.IO
import com.comcast.ip4s.{Host, Port}
import fs2.Chunk
import fs2.nats.client.{ClientConfig, NatsClient}

val config = ClientConfig(
  host = Host.fromString("localhost").get,
  port = Port.fromInt(4222).get
)

val program: IO[Unit] =
  NatsClient.connect[IO](config).use { client =>
    client.subscribe("hello.world").use { messages =>
      for
        _   <- client.publish("hello.world", Chunk.array("Hello, NATS!".getBytes))
        msg <- messages.take(1).compile.lastOrError
        _   <- IO.println(s"Received: ${msg.payloadAsString}")
      yield ()
    }
  }

Where to next

The code examples on every page are compiled against the published API as part of the build, so they stay in step with the library.