Understand Clojure software transactional memory as an optimistic in-process transaction model for refs, not as a lock replacement for every kind of state.
Software transactional memory (STM) coordinates changes to refs. It lets code update several in-process identities together without hand-written lock ordering.
The word “transaction” is deliberate, but the scope is narrower than a database transaction. Clojure STM protects in-memory refs in one JVM. It does not provide durability, cross-process coordination, or persistence after restart.
| Concept | Meaning in Clojure STM | Java engineer translation |
|---|---|---|
| Ref | A managed reference to one immutable value. | A coordinated identity, not a mutable field. |
dosync |
Starts a transaction over refs. | Transaction boundary. |
alter |
Applies a function to a ref’s current value. | Transactional read-modify-write. |
| Retry | Re-runs transaction logic after a conflict. | Optimistic concurrency retry. |
| Commit | Makes all ref changes visible together. | Atomic publish of related changes. |
The values inside refs should still be immutable. STM coordinates which value each ref points to; it does not make mutable Java objects safe internally.
STM assumes a transaction can usually succeed. If another transaction changes a ref in a conflicting way, Clojure retries the transaction body against a newer view.
That retry behavior changes how you design the code:
Put inside dosync |
Keep outside dosync |
|---|---|
| Pure calculations from old values to new values. | Email, HTTP calls, Kafka publishes, file writes. |
Ref updates with alter or ref-set. |
Mutating Java objects. |
| Invariant checks based on ref values. | Random ID generation that cannot be repeated safely. |
| Data describing work to perform later. | The irreversible work itself. |
If the transaction needs to trigger an external effect, record that intent as data, commit it, and let another part of the system perform the effect after commit.
1(def inventory (ref {:sku-1 10}))
2(def orders (ref []))
3
4(defn reserve! [sku qty]
5 (dosync
6 (let [available (get @inventory sku 0)]
7 (when (>= available qty)
8 (alter inventory update sku - qty)
9 (alter orders conj {:sku sku
10 :qty qty})))))
The inventory count and order record are separate identities, but the invariant spans both. Either both changes commit, or neither does.
| Requirement | Prefer |
|---|---|
| One independent value | Atom |
| Asynchronous serialized work | Agent, queue, channel, or executor |
| Durable business truth | Database or event log |
| State shared across JVMs | External coordinated store |
| Long-running workflow | Workflow engine, queue, or database-backed state machine |
Use STM when the coordination problem is in-process and genuinely spans several refs.