Browse Learn Clojure Foundations as a Java Developer

Coordinate State with Refs and STM

Use refs and software transactional memory when several in-process identities must change together, and keep transactions pure because Clojure may retry them.

Refs are Clojure’s coordinated state tool. Where an atom protects one independent identity, refs are for multiple identities that must change together inside a dosync transaction.

For a Java engineer, refs are closer to database transaction thinking than lock thinking. You describe the changes, Clojure’s software transactional memory (STM) gives the transaction a consistent view, and the changes commit as one unit or retry.

When Refs Fit

Scenario Why refs may fit
Transfer between accounts Debit and credit must commit together.
Move an item between queues The item must not disappear or appear twice.
Maintain related indexes Primary data and lookup index must agree.
Update one independent counter Usually too much; use an atom.
Call external services during update Bad fit inside the transaction.

A Transfer Example

 1(def checking (ref 500M))
 2(def savings  (ref 1000M))
 3
 4(defn transfer! [from to amt]
 5  (dosync
 6    (when (> amt @from)
 7      (throw
 8        (ex-info "low funds"
 9                 {:need amt
10                  :have @from})))
11    (alter from - amt)
12    (alter to + amt)))
13
14(transfer! checking savings 125M)

The two balances are separate identities, but the invariant spans both. The debit and credit should not be reviewed as two unrelated atom updates.

What Happens in a Transaction

STM behavior Engineering consequence
Reads see a consistent snapshot. Your transaction reasons over a stable view.
Ref changes commit at one logical point. Related facts do not become half-updated.
Conflicting transactions may retry. Transaction bodies must avoid irreversible side effects.
Ref values should be immutable. Persistent Clojure collections are the natural values to store.

Keep transaction bodies focused on state transitions. Do not send emails, write to Kafka, charge cards, or mutate Java objects inside dosync.

Ref Update Operations

Function Use
alter Apply a function to a ref in a coordinated transaction.
ref-set Set a ref to a new value inside a transaction.
commute Apply a commutative update where ordering is less strict.
ensure Protect a ref from change when you read it to validate another change.

Most business code should start with alter. Reach for commute only when you can clearly explain why the update is safe to reorder, such as adding independent counts.

Design Guidance

  • Prefer one immutable aggregate when the state naturally belongs together.
  • Use refs when separate identities are real and must coordinate.
  • Keep values in refs immutable.
  • Keep dosync small and side-effect free.
  • Move external I/O before or after the transaction, or record an event as data for another process to handle.

Knowledge Check

### When are refs a better fit than an atom? - [x] Several changing identities must be updated consistently as one transaction. - [ ] One independent counter must increment. - [ ] A value must be printed to the console. - [ ] A function must be defined in a namespace. > **Explanation:** Refs coordinate multiple identities through STM. A single independent value is usually simpler as an atom. ### Why should side effects be avoided inside `dosync`? - [x] STM transactions can be retried, so the side effect could happen more than once. - [ ] `dosync` only works with strings. - [ ] Refs cannot hold immutable values. - [ ] Java code cannot run in a transaction. > **Explanation:** Transactions are speculative until committed. Keep transaction bodies focused on pure state updates. ### What is the Java mental model that best matches refs? - [x] A transaction that coordinates related changes. - [ ] A mutable static field. - [ ] An anonymous inner class. - [ ] A logger. > **Explanation:** Refs are not just locks with different syntax. They provide transaction semantics for coordinated in-process state.
Revised on Saturday, May 23, 2026