Work through Java-to-Clojure loop translations for accumulation, filtering, early exit, indexed traversal, and recursive data so you choose the right Clojure construct instead of mechanically rewriting syntax.
The safest Java-to-Clojure translation is not mechanical. Preserve the loop’s purpose, then choose the Clojure construct that says that purpose directly.
This page uses common Java loop patterns and rewrites them into idiomatic Clojure.
Java often accumulates into a mutable local:
1public static int totalCents(List<Order> orders) {
2 int total = 0;
3
4 for (Order order : orders) {
5 total += order.totalCents();
6 }
7
8 return total;
9}
The direct Clojure version is reduce:
1(defn total-cents [orders]
2 (reduce (fn [total order]
3 (+ total (:total-cents order)))
4 0
5 orders))
For simple key extraction, map plus reduce may be clearer:
1(defn total-cents [orders]
2 (->> orders
3 (map :total-cents)
4 (reduce + 0)))
Use this shape when the Java loop’s real job is “combine many values into one value.”
Java code often mutates a target collection:
1List<String> emails = new ArrayList<>();
2
3for (User user : users) {
4 if (user.isActive()) {
5 emails.add(user.getEmail().toLowerCase(Locale.ROOT));
6 }
7}
Clojure should make selection and transformation separate:
1(require '[clojure.string :as str])
2
3(defn active-emails [users]
4 (->> users
5 (filter :active?)
6 (map :email)
7 (map str/lower-case)
8 vec))
The vec at the end is intentional. Many sequence operations are lazy, so use vec, mapv, into, or doall when you need an eager concrete collection.
Java’s break is common in searches:
1User result = null;
2
3for (User user : users) {
4 if (user.isAdmin()) {
5 result = user;
6 break;
7 }
8}
In Clojure, some communicates that you want the first truthy result:
1(defn first-admin [users]
2 (some (fn [user]
3 (when (:admin? user)
4 user))
5 users))
When you are reducing and need to stop early, use reduced:
1(defn first-over-limit [limit amounts]
2 (reduce (fn [_ amount]
3 (if (> amount limit)
4 (reduced amount)
5 nil))
6 nil
7 amounts))
Do not use reduced as a default style. Prefer some, take-while, drop-while, or first with filter when they express the search directly.
Java exposes the index naturally:
1for (int i = 0; i < names.size(); i++) {
2 System.out.println(i + ": " + names.get(i));
3}
In Clojure, make the index a value:
1(defn indexed-labels [names]
2 (map-indexed (fn [idx name]
3 (str idx ": " name))
4 names))
If the result is needed immediately, choose an eager target:
1(defn indexed-label-vector [names]
2 (mapv (fn [idx name]
3 (str idx ": " name))
4 (range)
5 names))
Some Java loops are not really collection transformations. They are small state machines:
1int retries = 0;
2
3while (!client.isReady() && retries < 3) {
4 client.refresh();
5 retries++;
6}
This is a good loop/recur case:
1(defn refresh-until-ready [ready? refresh!]
2 (loop [retries 0]
3 (cond
4 (ready?) :ready
5 (= retries 3) :timed-out
6 :else (do
7 (refresh!)
8 (recur (inc retries))))))
The state is still explicit, but it is not mutable. Each recur supplies the next value.
When the input shape is recursive, direct recursion is often the clearest expression. A tree is the classic example:
1(defn node-count [node]
2 (if node
3 (inc (reduce + 0 (map node-count (:children node))))
4 0))
This function is not tail-recursive because it must combine child results after recursive calls return. That is acceptable for small bounded trees, but for deep untrusted trees you should consider tree-seq, an explicit stack, or a library designed for the data structure.
| If the Java loop… | Prefer this Clojure shape | Why |
|---|---|---|
| Updates one accumulator | reduce |
Makes the accumulator a value |
| Builds a collection | map, filter, keep, into |
Separates transformation from storage |
| Stops after finding a match | some, first, reduced |
Encodes early exit without mutable flags |
| Walks with an index | map-indexed, range |
Treats position as data |
| Retries or advances state | loop/recur |
Keeps the state machine local and stack-safe |
| Mirrors nested structure | Direct recursion or traversal helpers | Matches the shape of the data |
flowchart LR
A["Java loop body"] --> B["Identify intent"]
B --> C["Choose Clojure construct"]
C --> D["Make state explicit as values"]
D --> E["Return data, not mutated containers"]
Rewrite these Java habits into Clojure:
For each rewrite, name the construct you chose before writing code. That habit prevents accidental Java-in-Clojure style.
reduce for accumulation, sequence functions for collection transformations, and loop/recur for explicit local state.recur is explicit, local, and tail-position-only.