Browse Learn Clojure Foundations as a Java Developer

Translating Java Loops to Clojure

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.

Pattern 1: Accumulating One Result

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

Pattern 2: Building a New Collection

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.

Pattern 3: Early Exit

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.

Pattern 4: Indexed Traversal

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))

Pattern 5: Explicit Local State

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.

Pattern 6: Recursive Data

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.

Translation Checklist

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"]

Practice

Rewrite these Java habits into Clojure:

  1. A loop that counts failed validations.
  2. A loop that copies active users into a new list.
  3. A loop that exits when a threshold is crossed.
  4. A loop that walks a tree of comments.

For each rewrite, name the construct you chose before writing code. That habit prevents accidental Java-in-Clojure style.

Key Takeaways

  • Translate loop intent, not loop syntax.
  • Use reduce for accumulation, sequence functions for collection transformations, and loop/recur for explicit local state.
  • Use direct recursion when the data is recursive, not merely because the Java version used a loop.
  • Remember that recur is explicit, local, and tail-position-only.

Quiz: Translating Java Loops to Clojure

### A Java loop appends transformed values to an `ArrayList`. What is the best Clojure starting point? - [x] A sequence pipeline ending in `vec` or `into` - [ ] A global atom - [ ] Direct recursion for every element - [ ] A Java `ArrayList` mutation loop > **Explanation:** The loop is building a new collection, so Clojure should express selection and transformation as data operations. ### Which Clojure construct is usually best for "find the first matching user"? - [x] `some` - [ ] `def` - [ ] `Thread` - [ ] `doseq` > **Explanation:** `some` can stop once it finds a truthy result, matching the intent of a Java search loop with `break`. ### Why might direct recursion be a poor translation for a simple summing loop? - [x] `reduce` expresses accumulation more directly and avoids unnecessary recursion mechanics. - [ ] Clojure cannot add numbers. - [ ] Java loops cannot be translated. - [ ] Direct recursion always mutates state. > **Explanation:** A summing loop is an accumulation problem, so `reduce` is the more idiomatic first choice. ### What should you use when a loop is a local retry state machine? - [x] `loop/recur` - [ ] `map` - [ ] `defonce` - [ ] `case` > **Explanation:** `loop/recur` handles explicit local state transitions without mutable variables. ### True or False: A recursive tree traversal is always tail-recursive. - [ ] True - [x] False > **Explanation:** Tree traversals often do additional work after child calls return, so they are commonly not tail-recursive.
Revised on Saturday, May 23, 2026