필사 모드: Modern Clojure 2026 — Clojure 1.12, Babashka, ClojureScript, shadow-cljs, Datomic, XTDB, re-frame Deep Dive
EnglishPrologue — Why Clojure in 2026?
May 2026. If someone asks, "does anyone still use Lisp?", the answer is clear. **Yes, and a lot of people do.** Brazil's Nubank is the world's largest digital bank with 110 million customers, and its backend is almost entirely written in Clojure. It runs more than 5,000 microservices and processes billions of transactions daily. CircleCI runs its core CI/CD pipeline on Clojure, Apple uses it for some internal tools, Cisco for network analytics, and Walmart Labs for its pricing engine.
Since Rich Hickey announced Clojure in 2007, the language has held the same philosophy for nearly two decades: **immutable first, functional, dynamically typed, hosted on the JVM, with concurrency as a first-class citizen**. But the ecosystem has quietly transformed in the 2020s. The single-build Leiningen era has given way to `deps.edn` and `tools.deps` as the new standard. ClojureScript has swallowed the entire npm ecosystem through shadow-cljs. And **Babashka**, compiled to a GraalVM native image, starts in roughly 20 ms — faster than Python — competing directly with Bash and Python in the shell scripting space.
As of May 2026, Clojure 1.12 has stabilized. It brought **qualified method invocation**, smarter Java interop, and smarter `partial` and `loop`. On the ClojureScript side, shadow-cljs 2.x has polished React 18, Vite 6, and esbuild integration. `re-frame 1.4` has refined the effects/events model. Datomic Pro became free for production use (Cloud remains paid). XTDB 2.0 layered SQL-first onto bitemporal, making it accessible to PostgreSQL users.
And critically, **the LLM era has given Clojure an unexpected tailwind**. REPL-driven development is fundamentally "evaluate one expression at a time and look at the result." That maps perfectly onto a workflow where the LLM generates code and a human verifies each step. Editors like Cursive, Calva, and CIDER have deepened LLM integration, and the pattern of evaluating LLM output directly in the REPL is becoming standard.
This article is **the full picture of Clojure in 2026** in one place. Language itself → build/deploy → backend stack → frontend → databases → testing → editors/REPL → production operations. We'll cover how Nubank runs 5,000 microservices, where the Korean and Japanese communities stand, and what stack a 2026 newcomer should pick.
> Clojure is not "a language with few users." It is **"a language carrying heavy traffic out of sight."** If 1% of the visible browser-side JS backends matter, then 1% of the invisible high-throughput backends are Clojure.
Pricing and version numbers move fast. Every figure in this post reflects **May 2026**, and focuses on structural differences. Even if minor versions change in six months, the decision framework should hold.
Chapter 1 · Clojure 1.12 — What Changed and Why It Matters
Clojure 1.12 first landed in September 2024, and by May 2026 the 1.12.x patches are stable and effectively the standard. Three core changes.
**Qualified method invocation**
This is the biggest change in Java interop. Previously you could only call `(.method obj arg)`, and treating a method as a value required a lambda wrapper like `#(.method % arg)`. Starting with 1.12, you can treat methods **as values**.
;; Before (1.11 and earlier)
(map #(.toUpperCase %) ["foo" "bar" "baz"])
;; => ("FOO" "BAR" "BAZ")
;; 1.12 — qualified method invocation
(map String/.toUpperCase ["foo" "bar" "baz"])
;; => ("FOO" "BAR" "BAZ")
;; Static methods
(map Math/abs [-1 -2 3])
;; => (1 2 3)
;; Constructors work too
(map StringBuilder/new ["hello" "world"])
The form `Class/.method` denotes an instance method; `Class/method` denotes a static method or constructor. It feels like Java lambdas, but stays Clojure-semantic.
**Inline optimization of `partial`**
`(partial f x)` returns a new function. Starting with 1.12, the compiler can inline it where possible, giving performance equivalent to a lambda. You can use `partial` freely on hot paths now.
**Type hints preserved through `loop`**
Type hints used to disappear across `loop`/`recur` cycles. That's fixed. Numeric code sees less boxing, with reported 5–15% wins.
Others: `clojure.core/iteration` — a generalization of lazy seqs. `update-vals`, `update-keys` — map transformation helpers. `add-tap` — standardized debugging hook. `random-uuid` — a clean UUID generator.
> Clojure 1.12 is not a "revolution"; it feels like "the hand tools were sharpened more." But Java interop becoming smoother is a big deal. You can now call Java libraries without friction.
Chapter 2 · tools.deps and deps.edn — Build After Leiningen
For long-time Clojure users, `project.clj` (Leiningen) is familiar. But in 2026 about 70% of new projects use `deps.edn` and `tools.deps`. Let's look at the differences.
**Basic `deps.edn` structure**
;; deps.edn
{:paths ["src" "resources"]
:deps {org.clojure/clojure {:mvn/version "1.12.0"}
ring/ring-core {:mvn/version "1.11.0"}
metosin/reitit {:mvn/version "0.7.0"}}
:aliases
{:dev {:extra-paths ["dev" "test"]
:extra-deps {nrepl/nrepl {:mvn/version "1.3.0"}
cider/cider-nrepl {:mvn/version "0.47.0"}}}
:test {:extra-paths ["test"]
:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.0"}}
:main-opts ["-m" "kaocha.runner"]}}}
Alias invocations like `clj -X:dev` define multiple execution modes. Maven coordinates work as-is, and Git coordinates are first-class.
;; A dependency directly from Git
{:deps {io.github.somelibrary/lib
{:git/url "https://github.com/somelibrary/lib.git"
:git/sha "a1b2c3d4..."}}}
Why this matters: you can use a library that isn't on Maven, a fork you maintain, or an internal library without standing up extra infrastructure.
**Is Leiningen dead?**
No. Leiningen still owns a big share of the ecosystem. `lein repl`, `lein test`, `lein uberjar` — all one-liners. The learning curve is gentler, and `project.clj` is code (not data), giving macros more flexibility. Downsides: slow startup (JVM + Lein boots in ~3s), and Maven-centric design makes things like Git coordinates feel awkward.
**Decision guide**
- New project + library variety matters → `deps.edn`
- Existing Leiningen project → keep `lein`
- Publishing libraries → both work, but `deps.edn` + `build.clj` (`tools.build`) is increasingly standard
**`tools.build`**
You write a `build.clj` script in Clojure. It's like Make or `package.json` scripts, but everything is Clojure.
;; A piece of build.clj
(ns build
(:require [clojure.tools.build.api :as b]))
(def lib 'com.example/my-lib)
(def version "0.1.0")
(def class-dir "target/classes")
(defn clean [_]
(b/delete {:path "target"}))
(defn jar [_]
(b/write-pom {:class-dir class-dir
:lib lib
:version version})
(b/jar {:class-dir class-dir
:jar-file (format "target/%s-%s.jar" (name lib) version)}))
Invoke with `clj -T:build jar`. Every build step is a Clojure function.
Chapter 3 · Babashka 1.4 — Clojure Has Entered the Shell
Babashka, created by Michiel Borkent (`borkdude`), is **a Clojure interpreter compiled to a GraalVM native image**. As of May 2026, 1.4.x is the stable line, with startup time at **20–30 ms**. That puts it in the same class as Python (~50 ms) and shell scripts (~10 ms).
**Why a shell script in Clojure?**
Three reasons.
1. **Data wrangling** — JSON, EDN, CSV, YAML are all first-class citizens. More expressive than `jq`.
2. **Error handling** — Bash's `set -e` is dangerous. Clojure exceptions and `try`/`catch` give robust scripts.
3. **Portability** — if you have the `bb` binary, the same code runs everywhere.
**Real example**
#!/usr/bin/env bb
(require '[babashka.curl :as curl]
'[cheshire.core :as json])
(defn fetch-github-stars [repo]
(-> (curl/get (str "https://api.github.com/repos/" repo))
:body
(json/parse-string true)
:stargazers_count))
(println "Stars:" (fetch-github-stars "clojure/clojure"))
Run as `bb script.clj` or `chmod +x` and run directly. No JVM needed.
**bb tasks**
Declare tasks in a `bb.edn` file. Similar to `npm run`, but with all of Clojure available.
;; bb.edn
{:tasks
{test (shell "clj -X:test")
build (shell "clj -T:build jar")
deploy {:depends [build]
:task (shell "clj -T:build deploy")}
clean (shell "rm -rf target")}}
Invoke as `bb test`, `bb build`, `bb deploy`. `:depends` declares dependencies between tasks.
**nbb — Node Babashka**
Babashka running on the Node.js runtime. You can use npm packages directly. Useful when you want to write short Clojure-style scripts on top of Node.
;; Calling an npm package from nbb
(require '["fs" :as fs])
(println (fs/readdirSync "."))
**squint — CLJS to JS**
Translates Clojure-style syntax to JS without compilation. Works even inside JSX. Gaining traction in small tooling.
> Babashka isn't trying to replace Python, borkdude has said — it wants to replace Bash. By 2026 that ambition looks realistic.
Chapter 4 · ClojureScript and shadow-cljs 2.x — Full Access to the npm Ecosystem
ClojureScript is a compiler that turns Clojure code into JavaScript. About 80% of ClojureScript projects in 2026 use **shadow-cljs** as their build tool. Created by Thomas Heller, its core value proposition is **npm integration**.
**shadow-cljs config example**
;; shadow-cljs.edn
{:source-paths ["src"]
:dependencies [[reagent "1.2.0"]
[re-frame "1.4.0"]
[metosin/reitit-frontend "0.7.0"]]
:builds
{:app {:target :browser
:modules {:main {:init-fn my-app.core/init}}
:output-dir "public/js"
:asset-path "/js"
:devtools {:after-load my-app.core/reload!
:http-root "public"
:http-port 8080}}}}
Running `npx shadow-cljs watch app` brings up a dev server with hot reload, a REPL, and live builds. The bundler is esbuild-based, so it's fast.
**Using npm packages directly**
This is shadow-cljs's killer feature. Add a package to `package.json` and import it from ClojureScript immediately.
(ns my-app.charts
(:require ["chart.js" :as Chart]
["d3" :as d3]))
(defn render-chart [el data]
(Chart/new el (clj->js {:type "bar"
:data data})))
`["chart.js" :as Chart]` — the string library name is the key. You can mix regular Clojure dependencies with npm packages in the same require block.
**Reagent — the classic React wrapper**
Reagent is the oldest ClojureScript wrapper on top of React. You author React components in Hiccup syntax.
(ns my-app.ui
(:require [reagent.core :as r]))
(defn counter []
(let [n (r/atom 0)]
(fn []
[:div
[:p "Count: " @n]
[:button {:on-click #(swap! n inc)} "+1"]])))
Vectors like `[:div ...]` become React elements. `r/atom` represents mutable state and triggers re-render on change. React 18 concurrency features are supported.
**Helix — the new React wrapper**
Helix is hook-first. Define functional components with the `defnc` macro.
(ns my-app.helix-ui
(:require [helix.core :refer [defnc $]]
[helix.hooks :as hooks]
[helix.dom :as d]))
(defnc Counter []
(let [[n set-n] (hooks/use-state 0)]
(d/div
(d/p "Count: " n)
(d/button {:on-click #(set-n inc)} "+1"))))
The `$` macro renders other components. Maps 1:1 to React Hooks. Many find it cleaner than JSX.
**UIx 2 — the most modern choice**
UIx 2 shares Helix's philosophy but emphasizes faster render optimization. SSR (server-side rendering) is supported and React Server Components compatibility is in progress. About 30% of new ClojureScript projects in 2026 pick UIx 2.
(ns my-app.uix-ui
(:require [uix.core :refer [defui $]]
[uix.dom]))
(defui counter []
(let [[n set-n] (uix.core/use-state 0)]
($ :div
($ :p "Count: " n)
($ :button {:on-click #(set-n inc)} "+1"))))
Chapter 5 · re-frame 1.4 — The State Management Answer for Large SPAs
You can build a counter with Reagent alone. But scattering atoms across components in a 100-screen SPA turns debugging into hell. **re-frame** is the answer. Built by the Day8 team, it's the de facto standard for state management in ClojureScript.
**re-frame's core model**
1. **app-db** — a single large map holding all state
2. **events** — keywords describing user actions (`:user/login`, `:cart/add-item`)
3. **handlers** — pure functions that transform app-db from events
4. **subscriptions** — functions that extract data needed by the view from app-db
5. **effects** — interactions with the outside world (HTTP, localStorage, timers)
(ns my-app.events
(:require [re-frame.core :as rf]))
;; Event handler
(rf/reg-event-db
:user/login-success
(fn [db [_ user]]
(assoc db :current-user user)))
;; Subscription
(rf/reg-sub
:user/current
(fn [db _]
(:current-user db)))
;; Use in a component
(defn user-panel []
(let [user @(rf/subscribe [:user/current])]
[:div "Hello, " (:name user)]))
;; Dispatch an event
(rf/dispatch [:user/login-success {:name "Min-yong Kim"}])
**Why re-frame**
Three reasons.
1. **Time-travel debugging** — the re-frame-10x library traces every event. You can rewind to any point.
2. **Pure functions first** — handlers are pure functions, easy to test.
3. **Effect isolation** — side effects like HTTP calls are explicit, so testing and mocking stay clean.
**What's new in re-frame 1.4**
- Interceptor chain optimizations — 30% faster dispatch in large apps
- Async effect support in `reg-fx`
- Stronger devtools integration — event traces in the Chrome Devtools panel
> "Reagent plus re-frame is the Clojure answer to React plus Redux. But where Redux is boilerplate hell, re-frame uses macros to do the same job in less code." — Day8 team summary.
Chapter 6 · Pathom 3 — The New Standard for Graph Queries
REST or GraphQL lets the client request what it needs from the server. But when data is spread across multiple backends (DB, external API, cache), "how do we assemble it?" is always a backend headache. **Pathom 3** solves that with a graph resolver model.
**Pathom 3's model**
You declare each data source as a **resolver**. The client asks for what it needs in EQL (EDN Query Language), and Pathom walks the resolver graph to gather the answer.
(require '[com.wsscode.pathom3.connect.indexes :as pci]
'[com.wsscode.pathom3.connect.operation :as pco])
;; Resolver — fetch user info by user-id
(pco/defresolver user-by-id [{:keys [user-id]}]
{::pco/output [:user/name :user/email]}
(let [user (db/find-user user-id)]
{:user/name (:name user)
:user/email (:email user)}))
;; Resolver — fetch orders by user-id
(pco/defresolver user-orders [{:keys [user-id]}]
{::pco/output [:user/orders]}
{:user/orders (db/find-orders-by-user user-id)})
(def env (pci/register [user-by-id user-orders]))
;; Client requests with a graph query
(p.eql/process env
{:user-id 42}
[:user/name :user/email :user/orders])
;; => {:user/name "Min-yong" :user/email "..." :user/orders [...]}
Pathom calls both resolvers and merges the results. The client doesn't know where data lives.
**Why it's good**
- You can refactor backends incrementally. Change a resolver's internals without changing client queries.
- N+1 issues are batched automatically by Pathom.
- Unlike GraphQL, the schema is co-located with code.
**Who uses it**
The backend standard for the Fulcro full-stack framework. Some of Nubank's microservices build their data aggregation layer on Pathom.
Chapter 7 · Datomic Pro and Cloud — Time as Data
Datomic is a database designed by Rich Hickey himself. Built by Cognitect, **the Pro license became free in 2023**, removing the entry barrier. In 2026 there are two flavors.
- **Datomic Pro** — self-hosted, free. Uses PostgreSQL, DynamoDB, Cassandra, etc. as storage.
- **Datomic Cloud** — serverless managed on AWS, paid.
**Core ideas of Datomic**
1. **immutable** — data is never overwritten. Every change is append-only.
2. **bitemporal** — separates transaction time (when it was recorded) from valid time (when it was true).
3. **EAV model** — entity-attribute-value triples. Similar to RDF.
4. **Separation of reads and writes** — peers/clients pull indexes into local memory and query. Only the transactor serializes writes.
;; Schema definition
(def schema
[{:db/ident :user/email
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/unique :db.unique/identity}
{:db/ident :user/name
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}])
(d/transact conn {:tx-data schema})
;; Insert data
(d/transact conn
{:tx-data [{:user/email "min@example.com"
:user/name "Min-yong Kim"}]})
;; Datalog query
(d/q '[:find ?name
:in $ ?email
:where
[?u :user/email ?email]
[?u :user/name ?name]]
(d/db conn)
"min@example.com")
;; => [["Min-yong Kim"]]
**bitemporal — time-travel queries**
;; Same query against the "as of Dec 1, 2025" DB
(d/q '[:find ?name ...]
(d/as-of (d/db conn) #inst "2025-12-01"))
;; Full change history of an entity
(d/history (d/db conn))
**Why use it**
- Fits financial and healthcare systems with strong audit requirements.
- You can add new attributes without schema migrations.
- Read caches live on the peer, so read-heavy workloads scale.
**Who uses it**
Nearly every Nubank backend microservice runs on Datomic (with their own infrastructure). Adoption is increasing in accounting, legal, and healthcare domains.
> Datomic is the embodiment of Rich Hickey's claim that "a database should be functional." It is no longer expensive — it's free. But it does require a shift in thinking.
Chapter 8 · XTDB 2.0 — Bitemporal Plus SQL-First
XTDB (formerly Crux) is a bitemporal database built by the UK-based JUXT team. As of 2026, **XTDB 2.0** is the stable release, with major changes from 1.x.
**1.x → 2.0 changes**
- Columnar storage (Apache Arrow-based) brings massive analytics query speedups
- **SQL becomes a first-class citizen** — Datalog is no longer mandatory
- Data lives in object storage (S3) — cost savings
-- XTDB 2.0 — time travel with standard SQL
SELECT name, email
FROM users
FOR VALID_TIME AS OF DATE '2025-12-01'
WHERE name LIKE 'Min%';
-- Specify both transaction time and valid time
SELECT *
FROM users
FOR SYSTEM_TIME AS OF TIMESTAMP '2026-01-15 10:00:00'
FOR VALID_TIME AS OF DATE '2025-12-01';
You can call it from Clojure too.
(require '[xtdb.api :as xt])
(xt/q node
'(from :users [name email]))
;; SQL works equally
(xt/q node "SELECT name, email FROM users")
**Why XTDB vs Datomic**
- XTDB's strength is **SQL compatibility**. Easier path from PostgreSQL.
- Datomic is **Datalog-first** and stronger on deeper graph queries.
- XTDB 2.0's columnar storage suits analytical workloads (OLAP).
- Datomic is more optimized for transactional workloads (OLTP).
**Datalevin and Datahike**
Embedded and on-device scenarios. Datalevin is an LMDB-backed embedded Datalog DB. Datahike is a distribution-friendly Datomic-like DB that can use IPFS as a backend. Great for mobile or Electron apps.
Chapter 9 · next.jdbc and HoneySQL — The Modern SQL Stack
Datomic and XTDB don't fit every scenario. Often you have to use existing PostgreSQL or MySQL. In 2026, the standard JDBC library in Clojure is **next.jdbc**, and the SQL DSL is **HoneySQL**.
**next.jdbc basics**
(require '[next.jdbc :as jdbc])
(def db {:dbtype "postgresql"
:dbname "myapp"
:user "postgres"
:password "secret"})
;; Query
(jdbc/execute! db ["SELECT id, name FROM users WHERE id = ?" 42])
;; => [{:users/id 42 :users/name "Min-yong"}]
;; Transaction
(jdbc/with-transaction [tx db]
(jdbc/execute! tx ["INSERT INTO users (name) VALUES (?)" "New User"])
(jdbc/execute! tx ["INSERT INTO accounts (user_id, balance) VALUES (?, ?)"
42 0]))
next.jdbc is the successor to clojure.java.jdbc — faster and a smaller API. Results are automatically returned as maps with keyword keys.
**HoneySQL — SQL as data**
(require '[honey.sql :as sql])
(sql/format
{:select [:id :name :email]
:from [:users]
:where [:and
[:= :status "active"]
[:> :age 18]]
:order-by [[:created-at :desc]]
:limit 10})
;; => ["SELECT id, name, email FROM users
;; WHERE status = ? AND age > ? ORDER BY created_at DESC LIMIT ?"
;; "active" 18 10]
You express SQL as an EDN map. Easy to compose conditions dynamically or extract clauses into functions. It's not an ORM — it's a **data-first SQL generator**.
Chapter 10 · Ring, Compojure, Reitit — The HTTP Server Trio
Almost every Clojure backend builds on **Ring**. Ring is an abstraction that represents HTTP requests and responses as plain maps. Various routing libraries sit on top of it.
**Ring basics**
The request is a map. The response is a map. The handler is a function.
(defn handler [request]
{:status 200
:headers {"Content-Type" "text/plain"}
:body "Hello, world"})
;; Run with the Jetty adapter
(require '[ring.adapter.jetty :as jetty])
(jetty/run-jetty handler {:port 3000})
Middleware is a higher-order function wrapping the handler.
(defn wrap-logging [handler]
(fn [request]
(println "Request:" (:uri request))
(handler request)))
(jetty/run-jetty (wrap-logging handler) {:port 3000})
**Compojure — the classic router**
(require '[compojure.core :refer [defroutes GET POST]])
(defroutes app
(GET "/" [] "Home")
(GET "/users/:id" [id] (str "User " id))
(POST "/users" req (create-user req)))
Good enough for small apps, but routes-as-code makes static analysis harder in larger apps.
**Reitit — the data-first router**
(require '[reitit.ring :as ring]
'[reitit.coercion.malli :as rcm])
(def app
(ring/ring-handler
(ring/router
[["/api"
["/users"
{:get {:handler list-users
:parameters {:query [:map [:limit int?]]}}
:post {:handler create-user
:parameters {:body [:map [:name string?] [:email string?]]}}}]
["/users/:id"
{:parameters {:path [:map [:id int?]]}
:get {:handler get-user}}]]]
{:data {:coercion rcm/coercion
:middleware [...]}})))
Routes are **data** (vectors and maps). You can automate middleware, input validation (coercion), and OpenAPI spec generation. Around 60% of new API projects in 2026 pick Reitit.
**Pedestal**
The Cognitect alternative. The interceptor chain model treats async and streaming as first-class. Steeper learning curve than Reitit but stronger for complex workflows like SSE or large file uploads.
Chapter 11 · Aleph, Manifold, core.async — Async and Streams
Clojure's concurrency model has three axes.
1. **core.async** — Go-channel style. CSP model.
2. **Manifold** — deferreds and streams. More functional-friendly.
3. **Aleph** — async HTTP/TCP server on top of Netty. Uses Manifold.
**core.async**
(require '[clojure.core.async :as async :refer [go chan <! >!]])
(let [c (chan 10)]
(go (>! c "hello")
(>! c "world"))
(go (println (<! c))
(println (<! c))))
`go` blocks are lightweight coroutines, and `<!` and `>!` receive and send on a channel. Go-flavored code on the JVM.
**Manifold**
(require '[manifold.deferred :as d]
'[manifold.stream :as s])
;; A deferred generalizes promise and future
(def result (d/deferred))
(d/chain result inc inc inc) ; function chaining
(d/success! result 1) ; => 4
;; A stream is an async sequence
(def s (s/stream))
(s/consume #(println "got:" %) s)
(s/put! s 42) ; prints "got: 42"
**Aleph server**
(require '[aleph.http :as http])
(defn handler [req]
;; Returning a deferred makes Aleph handle it asynchronously
(d/future
(Thread/sleep 100)
{:status 200 :body "done"}))
(http/start-server handler {:port 3000})
Aleph sits on Netty and handles tens of thousands of concurrent connections per server. WebSockets, SSE, and gRPC all feel natural.
> If core.async is Go's channels, Manifold is the Clojure expression of Scala's Future plus Akka Streams. They are complementary and fine to use together in one app.
Chapter 12 · Spec, Malli, Schema — Three Approaches to Validation
Clojure is dynamically typed, but it has plenty of tools for validating data shape. Three libraries compete.
**clojure.spec — the official answer**
(require '[clojure.spec.alpha :as s])
(s/def :user/email (s/and string? #(re-matches #".+@.+" %)))
(s/def :user/age (s/and int? #(<= 0 % 150)))
(s/def :user/user (s/keys :req-un [:user/email :user/age]))
(s/valid? :user/user {:email "min@example.com" :age 30})
;; => true
(s/explain :user/user {:email "bad" :age 30})
;; => :email failed: re-matches predicate
Use `s/keys` to define map shapes, and `s/and` and `s/or` to combine. Spec also supports **function argument and return specs**.
(s/fdef calc-tax
:args (s/cat :amount pos-int? :rate (s/and number? pos?))
:ret pos-int?)
Enabled with `stest/instrument`, calls are validated automatically.
**Malli — data-first and fast**
(require '[malli.core :as m])
(def User
[:map
[:email [:re #".+@.+"]]
[:age [:int {:min 0 :max 150}]]])
(m/validate User {:email "min@example.com" :age 30})
;; => true
(m/explain User {:email "bad" :age 30})
;; => {:errors [...]} detailed error
Malli's schemas are **data (EDN)**, easy to serialize and ship. They connect naturally to Reitit input validation, OpenAPI generation, and automatic docs. Most new projects in 2026 pick Malli.
**Plumatic Schema — the classic**
The oldest library. Macro-based, allowing compile-time checks. Heavy presence in legacy projects.
**How to pick**
- Want the official tool → Spec
- Data-first + Reitit/Malli integration → Malli
- Legacy compatibility → Schema
Chapter 13 · Mount, Integrant, Component — DI and Lifecycle
Large systems care about startup order (DB connection → cache → HTTP server → background jobs) and shutdown order. Clojure has three libraries for it.
**Mount — the simplest**
(require '[mount.core :as mount])
(mount/defstate db
:start (open-connection "jdbc:...")
:stop (close-connection db))
(mount/defstate server
:start (start-server {:port 3000})
:stop (stop-server server))
(mount/start) ; start everything
(mount/stop) ; shut down in reverse order
Uses global state but stays simple. Great for small projects.
**Component — Stuart Sierra's classic**
(defrecord Database [config conn]
component/Lifecycle
(start [this] (assoc this :conn (open-connection config)))
(stop [this] (close-connection conn) (assoc this :conn nil)))
(def system
(component/system-map
:db (->Database db-config nil)
:server (component/using (->Server {}) [:db])))
Dependencies are declared explicitly. Easy to swap components in tests.
**Integrant — data-first Component**
(require '[integrant.core :as ig])
(def config
{:db/connection {:url "jdbc:..."}
:server/jetty {:db (ig/ref :db/connection) :port 3000}})
(defmethod ig/init-key :db/connection [_ {:keys [url]}]
(open-connection url))
(defmethod ig/init-key :server/jetty [_ {:keys [db port]}]
(start-server {:db db :port port}))
(def system (ig/init config))
(ig/halt! system)
Configuration is EDN data, so environment-specific config files split cleanly. Pairs nicely with Reitit. The most common choice for new projects in 2026.
Chapter 14 · Testing — From clojure.test to Generative
**clojure.test — the standard**
(require '[clojure.test :refer [deftest is testing]])
(deftest user-creation-test
(testing "valid input creates user"
(is (= "Min-yong" (:name (create-user {:name "Min-yong"})))))
(testing "missing email throws"
(is (thrown? Exception (create-user {})))))
Built in. Feels like JUnit but more functional.
**Kaocha — modern runner**
A runner on top of clojure.test. Colored output, watch mode, plugin system, JUnit XML reports.
clj -M:test # or bb test
Configured via `tests.edn`. Great for deterministic output in CI.
**Eftest**
clojure.test compatible. Strong watch mode and parallel execution.
**clojure.test.check — Property-based testing**
(require '[clojure.test.check :as tc]
'[clojure.test.check.generators :as gen]
'[clojure.test.check.properties :as prop])
(def reverse-twice-is-identity
(prop/for-all [v (gen/vector gen/int)]
(= v (reverse (reverse v)))))
(tc/quick-check 100 reverse-twice-is-identity)
;; => Validates with 100 random inputs
The Clojure port of QuickCheck. Properties are validated by randomly generated inputs.
**clojure.spec.test — integrates with Spec**
(require '[clojure.spec.test.alpha :as stest])
(stest/check `calc-tax)
;; => Generative testing of functions defined via Spec fdef
If you have Spec specs, generative tests run automatically.
Chapter 15 · Editors — Cursive, Calva, CIDER, Conjure
Clojure development is **REPL-centered**. Instead of saving files and restarting, you send one expression at a time to a running REPL and see results immediately. That makes editor choice more decisive than in other languages.
**Cursive — IntelliJ IDEA plugin**
The most polished commercial option. Strong code intelligence (autocomplete, refactoring, static analysis). Dominant when precise navigation and refactoring matter in large projects. Personal license about $99/year, commercial separate. A free EAP build exists.
**Calva — VS Code extension**
Free and open-source, the most popular editor. REPL integration, paredit editing, inline evaluation results, jack-in automation. The first pick if you use VS Code. An informal survey suggests about 45% of Clojure users in 2026 use Calva.
**CIDER — Emacs**
The oldest and most powerful. REPL integration, debugger, tracing, step-by-step macro expansion visualization — all first-class. Near-default for existing Emacs users.
**Conjure — Neovim**
The answer for Neovim users. tmux-friendly, lightweight, with all the core features.
**File → REPL flow**
Cursive, Calva, and CIDER all share the same flow. Open a file, press the "evaluate this form in REPL" key (`Cmd-Enter`, `Cmd-Shift-Enter`, etc.), and the expression goes to the REPL. Modify the function, re-evaluate, and the new definition is live in the running system. No server restart.
> "The moment you send code to the REPL is the biggest reason to use Clojure. There's no compile-run-stop cycle. Code is always alive." — Bruce Hauman, author of Figwheel.
Chapter 16 · REPL-Driven Development — Why It Wins in the LLM Era
REPL-driven development is the beating heart of Clojure culture. The idea is simple.
1. Keep a REPL running all the time.
2. Your code lives in files, but you execute one expression at a time in the REPL.
3. Look at the result, modify the code, and re-evaluate.
4. When satisfied, leave the code in the file and move on.
**Why it shines more in the LLM era**
LLMs generate code blocks. A human has to verify them. Two ways to verify.
- Traditional: paste into a file, run build and tests, look at output. Long cycle (tens of seconds to minutes).
- REPL: paste the LLM-generated expression directly into the REPL. Result instantly. If wrong, ask again and re-evaluate.
Clojure fits this workflow intrinsically. Data is expressible as EDN, so LLM-generated data can be evaluated in the REPL as-is. The macro system is strong, letting LLMs write domain-specific code more concisely.
**Real LLM + REPL workflow**
;; Ask the LLM: "Write a function that filters users in their 30s"
;; Evaluate the response in the REPL:
(defn users-in-30s [users]
(filter #(<= 30 (:age %) 39) users))
;; Test it right away
(users-in-30s [{:name "A" :age 25}
{:name "B" :age 35}
{:name "C" :age 45}])
;; => ({:name "B" :age 35})
;; Works. Save it to the file.
Verify LLM output without long build cycles. As of 2026, Cursive and Calva both ship shortcuts for sending LLM output directly to the REPL.
Chapter 17 · GraalVM Native Image — Clojure as a Native Binary
GraalVM ahead-of-time (AOT) compiles JVM bytecode into a native executable. Compiling a Clojure program with GraalVM Native Image brings startup down to ~20 ms (vs 1–2 seconds for the JVM).
**When it's useful**
- CLI tools (Babashka is exactly this category)
- AWS Lambda and other cold-start-sensitive serverless
- Tiny microservices (e.g., a single health-check endpoint)
**Limitations**
- Reflection and dynamic class loading are restricted. Some Clojure patterns break because the language is dynamic.
- Build time is long (minutes).
- Memory footprint is small, but peak performance may be lower than JIT.
**A real example**
Babashka itself is the biggest success story. The `bb` binary is a roughly 80 MB statically linked binary compiled with GraalVM. Startup ~20 ms, memory ~30 MB. Lighter than Python.
To compile your own app with native-image, use helper libraries like `org.clj-easy/graal-build-time` and a build tool like `clj.native-image`. You need to set JVM compatibility options carefully (such as `--initialize-at-build-time`).
Chapter 18 · The Nubank Case — 100 Million Users on Clojure
Nubank is the largest production case study in the Clojure world. A Brazilian digital bank with **110 million customers, more than 5,000 microservices, and billions of daily transactions** as of 2026. Its backend is almost entirely Clojure.
**Why they chose Clojure**
Co-founder and CTO Edward Wible has explained it in numerous public talks. Three reasons.
1. **Functional + immutable data** — financial data must never be modified arbitrarily. Immutability provides that safety net naturally.
2. **Datomic** — every transaction history must be retained bitemporally. Datomic's model is a perfect fit.
3. **REPL-driven development** — they keep REPLs open in production to investigate live data. Immediate diagnosis when an incident occurs.
**Architecture overview**
- Every microservice in Clojure (some Kotlin/Go too)
- Datomic as the main DB (their own Pro license, DynamoDB backend on AWS)
- Kafka for inter-service event streams
- Pedestal/Aleph-based HTTP servers
- Schema (legacy) and Malli (new) for data validation
- A custom Pathom-like graph query layer
**Team operations**
About 2,000 engineers work in Clojure. Prior Clojure experience is not required for hires — they teach it in an internal bootcamp. People with functional thinking adapt quickly, per their policy.
**Performance**
- p99 response time: under 50 ms (most endpoints)
- Availability: 99.99% or better
- Per microservice average: about 5 instances, 512 MB memory
> "Choosing Clojure was one of the best business decisions we made. Simplicity made us fast." — Edward Wible, Nubank CTO.
Chapter 19 · Korean and Japanese Communities — Where Things Stand
**Clojure Korea**
A community centered in Seoul. Slack channel `clojure-kr`, regular meetups, pair-programming sessions. First-generation developers like Mun-yong Kim are active. Some teams inside Kakao and Naver also use Clojure (public materials are sparse). Korean resources include translated books from Wikibooks/Insight Publishing and GitHub Korean tutorials.
**Clojure Tokyo / Clj-jp**
The Japanese community is more active than Korea's. Regular Tokyo meetups, Fukuoka and Osaka chapters, and an annual conference `Clj-jp Conf`. Some Japanese companies use it in production, including Drip (alcohol), Cookpad (cooking recipes), and Recruit. Lots of Japanese learning material.
**Asia conferences**
- **Clojure/conj** — the US flagship (annual)
- **Clojure/north** — Canada
- **re:Clojure** — UK
- **EuroClojure** — Europe
- **Clj-jp Conf** — Japan
In 2026 there is movement to revive a small Korean Clojure conference.
Chapter 20 · Stack Recommendations for 2026 Starters
By background.
**From Java/Kotlin**
- Clojure 1.12 + Leiningen → familiar build tool to start
- IntelliJ IDEA + Cursive
- Ring + Compojure (small apps) or Reitit (medium/large)
- PostgreSQL + next.jdbc + HoneySQL
- First book: "Programming Clojure (4th edition)"
**From JavaScript/TypeScript**
- ClojureScript + shadow-cljs
- VS Code + Calva
- Reagent + re-frame (battle-tested) or UIx 2 (modern)
- Start by reusing npm packages directly
- First book: "Learn ClojureScript" (free, online)
**From Python/Ruby**
- Start with Babashka for shell scripts
- VS Code + Calva
- Datomic Pro or SQLite + next.jdbc
- The biggest mind shift is learning REPL-driven development
- First resource: the official site's "Getting Started"
**From Lisp (Scheme, CL)**
- Learning curve is very fast
- Cursive or Emacs+CIDER
- You only have to adapt to the stronger functional emphasis
- The macro system is similar to Common Lisp's
**Common learning path**
1. **Open the REPL and experiment line by line** — `lein repl` or `clj`
2. **Practice working with data** — `map`, `filter`, `reduce`, `into`, `update-in`, `select-keys`
3. **State management** — differences between `atom`, `ref`, `agent`
4. **Sequence abstractions** — lazy seqs, transducers
5. **Macros — last**
> Some people say they "learned Clojure in a week." That's technically possible. But the real start is the six months after that. Functional thinking takes time.
Chapter 21 · Common Doubts, Answered
**Q: It's dynamically typed — won't it break at runtime?**
A: Partially true. But Clojure specifies data shape with EDN and Spec or Malli. Validate at function boundaries and stay dynamic inside. Large systems still run well — Nubank is proof.
**Q: The talent pool is small, right?**
A: Yes, small. But **the remaining talent is strong**. Hiring for Clojure filters for functional thinking. Reports consistently show a more senior team on average. Nubank's hiring data: attrition is half the average of other fintechs.
**Q: Isn't hiring hard?**
A: Two answers. (1) Hire from other languages and teach. Clojure can be picked up quickly. (2) Using Clojure itself becomes a magnet for good engineers.
**Q: What about performance?**
A: On the JVM, JIT-compiled code performs at Java levels. You can optimize hot paths by avoiding boxing, using type hints, and so on. Nubank's p99 of 50 ms is on par with or better than typical Java or Go backends.
**Q: Learning curve?**
A: Two steep spots. (1) Adapting to parentheses and prefix notation — usually about a week. (2) Functional thinking — about three months on average. Flat after that.
**Q: Java library interop?**
A: First-class. Use `(.method obj arg)`, or from 1.12 `Class/.method` forms. Nearly every Java library works. The biggest advantage of being JVM-hosted.
**Q: When does Clojure not fit?**
A: Three cases. (1) Hard real-time that cannot tolerate any GC pause. (2) Embedded systems with very tight memory budgets (though Babashka helps somewhat). (3) When the entire team is opposed to functional programming.
Chapter 22 · Beyond 2026
**Expected directions**
1. **Deeper AI/LLM integration** — REPL-driven development and LLMs mesh well, so IDE integration will get deeper. Cursive and Calva will pull tools like Claude and Cursor closer.
2. **A ClojureScript revival** — UIx 2 meets React Server Components, and runtime support for Bun and Deno is in progress.
3. **Babashka's domain widens** — shell scripts → CLI tools → small servers. With nbb and squint, the surface area keeps growing.
4. **Datomic vs XTDB competition** — as the bitemporal DB market grows, both camps will accelerate development.
5. **GraalVM Native Image stabilization** — more Clojure libraries guarantee native-image compatibility.
6. **WebAssembly compilation** — ClojureScript-to-WASM experiments are in progress, opening uses beyond the browser.
**Risk factors**
- Rich Hickey's succession — Hickey retired from Nubank in 2022. The Cognitect team has taken over well, but long-term consistency without one person's vision is being tested.
- Datomic's future — after Cognitect was acquired by Nubank, how much of Datomic becomes open source remains unclear.
- Competition from other functional languages — Elixir, Gleam, Roc, and others are rising. How does Clojure stay differentiated?
**Optimistic scenario**
Nubank's success spreads to other fintech, bio, and health domains. Gradual adoption in Korean and Japanese enterprises. ClojureScript's React integration becomes smoother, capturing meaningful share in the frontend market.
Chapter 23 · Conclusion — Why Clojure Survived
Few languages have held a consistent identity for nearly two decades. Rich Hickey's principle — "simple is better than easy" — underlies every Clojure decision. The result: Clojure didn't chase trends. It walked its own road.
That's why it survived. Fintech, healthcare, accounting, compliance — **domains where correctness matters more than speed** — Clojure is dominant. That Nubank serves 100 million users on it is not a coincidence.
If you're starting Clojure in 2026, one piece of advice.
> Don't try to learn it fast. **Open a REPL, and grow into it one expression at a time, slowly.** Clojure isn't learned all at once. It's cultivated one expression at a time. Six months later, you won't want to go back.
Good luck. Spin up the REPL and begin.
References
1. Clojure official site — https://clojure.org
2. Clojure 1.12 Release Notes — https://github.com/clojure/clojure/blob/master/changes.md
3. Rich Hickey, "Simple Made Easy" — https://www.youtube.com/watch?v=SxdOUGdseq4
4. Rich Hickey, "Hammock Driven Development" — https://www.youtube.com/watch?v=f84n5oFoZBc
5. Babashka official — https://babashka.org
6. Michiel Borkent, "Babashka: A native Clojure interpreter for scripting" — https://blog.michielborkent.nl
7. ClojureScript official — https://clojurescript.org
8. shadow-cljs official — https://github.com/thheller/shadow-cljs
9. re-frame official — https://day8.github.io/re-frame
10. Reagent official — https://reagent-project.github.io
11. Helix official — https://github.com/lilactown/helix
12. UIx 2 official — https://github.com/pitch-io/uix
13. Pathom 3 official — https://pathom3.wsscode.com
14. Datomic official — https://www.datomic.com
15. XTDB 2.0 official — https://xtdb.com
16. next.jdbc official — https://github.com/seancorfield/next-jdbc
17. HoneySQL official — https://github.com/seancorfield/honeysql
18. Reitit official — https://github.com/metosin/reitit
19. Pedestal official — https://pedestal.io
20. Aleph official — https://github.com/clj-commons/aleph
21. Manifold official — https://github.com/clj-commons/manifold
22. core.async official — https://github.com/clojure/core.async
23. clojure.spec official — https://clojure.org/about/spec
24. Malli official — https://github.com/metosin/malli
25. Integrant official — https://github.com/weavejester/integrant
26. Kaocha official — https://github.com/lambdaisland/kaocha
27. clojure.test.check official — https://github.com/clojure/test.check
28. Cursive official — https://cursive-ide.com
29. Calva official — https://calva.io
30. CIDER official — https://docs.cider.mx
31. Nubank Engineering Blog — https://building.nubank.com.br/tag/clojure
32. Edward Wible, "Why Clojure?" — https://www.youtube.com/results?search_query=edward+wible+nubank+clojure
33. State of Clojure 2024 Survey — https://clojure.org/news/2024/05/01/state-of-clojure-2024
34. Clojure Korea Slack — https://clojurians.slack.com (the #clojure-kr channel)
35. Clj-jp official — https://clj-jp.org
36. GraalVM Native Image — https://www.graalvm.org/native-image
37. clj.native-image — https://github.com/taylorwood/clj.native-image
38. Programming Clojure (4th ed) — https://pragprog.com/titles/shcloj4/programming-clojure-fourth-edition
39. Learn ClojureScript (free book) — https://www.learn-clojurescript.com
40. ClojureVerse community forum — https://clojureverse.org
현재 단락 (1/669)
May 2026. If someone asks, "does anyone still use Lisp?", the answer is clear. **Yes, and a lot of p...