Most Clojure NATS clients are thin wrappers around the official Java SDK (nats.java). That's a perfectly reasonable choice, but it comes with a JVM tax: you get nats.java's threading model, its option-builder classes, and a hard dependency on a full JVM, which means no runtimes like Babashka.
claxon takes the other path. The NATS client protocol is a small, text-based, line-oriented protocol. claxon implements the protocol directly against a plain java.net.Socket, using nothing but Clojure data to describe the wire format with the following goals:
bb.edn project, or embedded in a larger bb-based tool, with no AOT compilation and no native dependencies beyond the JVM/GraalVM that bb already ships.claxon.conf/defaults's :claxon/frame-shapes) ops, their arguments, and their payloads. Reading and writing frames are both generic interpreters over that data, not one function per operation. Additionally, the default protocol behaviour can be influenced and new things added, all from the userland.nats.java. The only third-party dependency is a JSON library (clojure.data.json on the JVM, nothing on bb), used only for INFO/CONNECT payloads.assoc-able data.claxon is deliberately a protocol client, not a full-featured NATS SDK. It doesn't try to be a drop-in replacement for nats.java's surface area, see Roadmap for what's intentionally left out for now.
flowchart TB
App["your application code"]
Client["claxon.client<br/><b>connect · invoke · add-handler · remove-handler · close</b><br/><i>public API</i>"]
subgraph Impl["claxon.impl.* and claxon.conf (implementation detail)"]
direction LR
Conf["claxon.conf<br/><i>defaults,<br/>:claxon/frame-shapes</i>"]
Write["claxon.impl.write<br/><i>snd: encodes &<br/>writes a frame</i>"]
Read["claxon.impl.read<br/><i>read-frame: decodes<br/>a frame from bytes</i>"]
Sock["claxon.impl.sock<br/><i>->tls: upgrades<br/>the socket</i>"]
Common["claxon.impl.common<br/><i>dispatch, submap?,<br/>parse-nats-url, json</i>"]
end
Socket["java.net.Socket<br/>TCP, optionally TLS"]
Server(("NATS server"))
Loop["background reader loop<br/>on :claxon/executor<br/><i>read-frame → dispatch →<br/>matching handlers</i>"]
App -->|"connect / invoke / add-handler"| Client
Client --> Conf & Write & Read & Sock & Common
Conf --> Write
Write & Read & Sock --> Common
Write -->|writes| Socket
Socket -->|"PUB / SUB / PING / ..."| Server
Server -->|"MSG / PING / ..."| Socket
Socket -->|reads| Read
Read --> Loop
Loop -->|"runs your handler fn"| App
classDef api fill:#eef2ff,stroke:#6366f1,color:#3730a3
classDef impl fill:#fafafa,stroke:#a1a1aa,color:#3f3f46
classDef sock fill:#1e293b,stroke:#0f172a,color:#f1f5f9
classDef loop fill:#fff1f2,stroke:#fb7185,color:#9f1239
classDef app fill:#f8fafc,stroke:#cbd5e1,color:#0f172a
class Client api
class Conf,Write,Read,Sock,Common impl
class Socket sock
class Loop loop
class App,Server app
| aspect | claxon | clj-nats | monkey-projects/nats |
|---|---|---|---|
| Underlying impl | Pure Clojure, raw sockets | Wraps nats.java | Wraps nats.java |
| Babashka compatible | Yes | No | No |
| User modifiable protocols | Yes | No | No |
| Dependencies | data.json on JVM, none on bb | nats.java + its transitive deps | nats.java |
| Protocol description | Data-driven | Delegated to nats.java | Delegated to nats.java |
The following are not yet implemented but planned (based on asks/issues) in order of priority:
INFO nonce yet.Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |