Browse Clojure Foundations for Java Developers

Software Transactional Memory (STM)

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.

When STM is the right tool

Use STM when:

  • you have multiple related state identities
  • their updates must stay coordinated
  • a partially applied update would be invalid

A classic example is transferring money between two accounts. Decrementing one account and incrementing the other should be treated as one logical change.

Refs are the state holder for STM

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 transaction

Coordinated 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.

Why this is different from an atom

An atom is usually right when one identity changes independently.

STM with refs is for cases where multiple identities must change together.

A practical rule:

  • one independent state cell: usually atom
  • multiple coordinated state cells: maybe ref and STM

If 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 commute

The 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.

Why side effects inside STM are risky

Transactions may retry. That means the body of dosync is not a good place for side effects that must happen exactly once, such as:

  • sending an email
  • writing a one-time audit event externally
  • charging a credit card

If the transaction retries, your side effect logic may run more than once or behave inconsistently.

The safe pattern is:

  • use STM to coordinate in-memory state transition
  • perform non-transactional side effects outside that retryable core

Java comparison that helps

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.

Common beginner mistakes

Using STM for single independent counters or flags

That usually wants an atom, not a ref.

Putting irreversible side effects inside dosync

Transactions can retry, so “run once” side effects do not belong in the transaction body.

Using refs without a real invariant that spans identities

If nothing actually needs coordination, STM adds complexity you do not need.

A practical rule

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.

Knowledge Check

### What problem is Clojure STM primarily designed to solve? - [x] Coordinated updates to multiple pieces of shared state that must remain consistent together - [ ] Fast immutable vector creation - [ ] Stateless parallel computation only - [ ] Java interop with synchronized methods > **Explanation:** STM is for coordinated shared-state changes across multiple identities, not for every kind of state or performance problem. ### Why would `ref` plus `dosync` be a better fit than an `atom` in some designs? - [x] Because the update involves multiple identities that should change together as one transaction - [ ] Because atoms cannot hold immutable data - [ ] Because refs are always faster than atoms - [ ] Because atoms only work in single-threaded code > **Explanation:** Atoms are great for independent state. Refs are for coordinated state transitions where consistency spans more than one identity. ### Why is it risky to put irreversible side effects inside a `dosync` block? - [x] Because the transaction may retry, so the side effect may run more than once or at the wrong time - [ ] Because `dosync` forbids all function calls - [ ] Because refs cannot be read after a side effect - [ ] Because STM only works with pure math functions > **Explanation:** STM transactions can retry under contention. Side effects that assume one-time execution should stay outside the retryable transactional core.
Revised on Friday, April 24, 2026