Browse Learn Clojure Foundations as a Java Developer

Understand Software Transactional Memory

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.

STM in One Table

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.

The Optimistic Model

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.

A Small Example

 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.

When STM Is Not the Answer

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.

Knowledge Check

### What does Clojure STM coordinate? - [x] In-process ref changes inside a JVM. - [ ] Durable database commits across services. - [ ] All Java object field mutations. - [ ] Browser UI updates. > **Explanation:** STM coordinates Clojure refs in memory. Durability and cross-process consistency require another system. ### Why should transaction bodies stay pure? - [x] Clojure may retry the transaction body after a conflict. - [ ] `dosync` cannot read values. - [ ] Refs only support strings. - [ ] STM runs only at compile time. > **Explanation:** Retried transaction bodies can duplicate irreversible work. Keep effects outside `dosync`. ### What Java mental model is closest to refs and STM? - [x] Optimistic transactions over related identities. - [ ] A mutable static field. - [ ] A thread created with `new Thread`. - [ ] A synchronized getter. > **Explanation:** Refs and STM are about coordinated transactional updates, not general-purpose field locking.
Revised on Saturday, May 23, 2026