Create Clojure atoms for independent in-process state, read them safely with deref, and design the stored value as immutable data rather than a mutable Java object.
An atom is a reference to one current value. The atom can point to a newer value over time, but the value itself should be ordinary immutable Clojure data.
That distinction matters for Java engineers. In Java, mutable state often hides behind object fields and setter methods. In Clojure, an atom makes the changing identity explicit and keeps the domain rules in functions that transform data.
| Operation | What it does | Java comparison |
|---|---|---|
(atom value) |
Creates a new atom holding value. |
new AtomicReference<>(value) |
@state |
Reads the current value. | ref.get() |
(deref state) |
Same read, written as a function call. | ref.get() |
(swap! state f) |
Replaces the value with (f old-value). |
updateAndGet style update |
(reset! state v) |
Replaces the value directly. | set, but on the atom reference |
1(def service
2 (atom {:status :starting
3 :requests 0}))
4
5@service
6;; => {:status :starting, :requests 0}
7
8(get @service :status)
9;; => :starting
The map is not mutated. A later update will produce a different map, and the atom will point to that newer map.
The atom should usually sit at a boundary: application lifecycle state, a small in-memory cache, a metrics snapshot, or a REPL-managed development system. Do not wrap every nested field in its own atom.
| Design choice | Consequence |
|---|---|
| One atom holding a related map | Updates can preserve invariants inside that map. |
| Many tiny atoms for related facts | Readers may observe combinations that never existed logically. |
| Atom holding immutable data | Safe reads and functional updates. |
| Atom holding a mutable Java object | Atomic reference replacement does not make the object internals safe. |
If two facts must always change together, prefer one immutable aggregate in one atom, or use refs if they are genuinely separate identities.
Reading an atom gives you the current value at that instant. Another thread may update the atom immediately after your read. That is normal; design code so a read value is a stable snapshot, not a lock.
1(defn service-ready? []
2 (= :ready (:status @service)))
This function answers a question about one observed value. It does not prevent later updates.
| Java habit | Clojure adjustment |
|---|---|
| Add synchronized getters and setters. | Prefer a small atom plus pure update functions. |
| Store mutable collections behind an atomic reference. | Store persistent Clojure maps, vectors, or sets. |
| Spread state across many fields. | Group facts by invariant and update boundary. |
| Read a value and then mutate it in place. | Use swap! to compute a replacement value. |