What Clojure STM is, when to use refs and `dosync`, and how Java developers should think about coordinated state changes without explicit locks.
Software Transactional Memory, usually shortened to STM, is Clojure’s model for coordinated updates to multiple pieces of shared state. If you come from Java, the closest intuition is “transaction-style shared-state coordination without hand-written lock choreography.”
STM exists because some state changes belong together. Updating one identity without the other would leave the system inconsistent.
Use STM when:
A classic example is transferring money between two accounts. Decrementing one account and incrementing the other should be treated as one logical change.
In Clojure, STM works with refs.
1(def checking (ref 1000))
2(def savings (ref 500))
Each ref points to an immutable value, but the ref itself can move to a new value through a transaction.
dosync defines a transactionCoordinated ref updates happen inside dosync.
1(defn transfer! [from to amount]
2 (dosync
3 (alter from - amount)
4 (alter to + amount)))
That means the two updates succeed together as one transaction or are retried together if there is contention.
For Java developers, this is the important mindset shift: you are declaring a coordinated state transition, not manually locking two objects and hoping the ordering stays correct forever.
atomAn atom is usually right when one identity changes independently.
STM with refs is for cases where multiple identities must change together.
A practical rule:
atomref and STMIf you use STM for every piece of state, you are probably overengineering. If you use atoms for truly coordinated multi-identity invariants, you may push consistency problems into application logic.
alter and commuteThe usual STM update tool is alter, which applies a function to the current ref value as part of the transaction.
commute is a more specialized option for updates where operation order does not matter in the same way. It can reduce contention in the right situations, but it is not the beginner default.
For most early code, understanding ref, dosync, and alter is the important foundation.
Transactions may retry. That means the body of dosync is not a good place for side effects that must happen exactly once, such as:
If the transaction retries, your side effect logic may run more than once or behave inconsistently.
The safe pattern is:
In Java, multi-object consistency often leads to locks, lock ordering rules, and careful review of who holds which resource when.
Clojure STM changes the conversation. Instead of hand-managing locks, you describe the coordinated state update and let the STM system manage transactional consistency.
That does not mean STM is free or always the best choice. It means it is often a clearer fit for coordinated shared state than manual locking.
That usually wants an atom, not a ref.
dosyncTransactions can retry, so “run once” side effects do not belong in the transaction body.
If nothing actually needs coordination, STM adds complexity you do not need.
Reach for STM when the business rule is really “these multiple state changes must stay consistent together.” If the state does not have that kind of invariant, use a simpler tool.