Why some programmers still pick Clojure when the problem gets messy
Elena Vasquez
September 18, 2026
I would not start a CRUD app in Clojure. I have said that in rooms where it made me unpopular. A form, a Postgres table, a login box — Laravel, Rails, Spring, Next, take your pick. Clojure’s cost is cultural and hiring-shaped. You pay it when the problem is messy enough that the cost of a weaker model is higher than the cost of a smaller talent pool.
Some programmers still pick it anyway. I have been one of them, twice, and I have walked away once. The walk-away was a product that had become CRUD with extra parentheses. The stays were systems where data arrived wrong, rules stacked, and the useful program was a pipeline of transformations you could still hold in your head.
What “messy” means here
Messy is not “we have a lot of tickets.” Messy is: the input is a pile of maps from three vendors, each almost the same; the output is a decision that must be explained; the rules change weekly; the state you thought was an object is actually a value at a time. Insurance rating. Market data normalization. A rules engine that product keeps calling “just a few ifs.” ETL that grew a conscience.
In those systems, Java’s noun-heavy graph and TypeScript’s object soup both start to smell. You spend the week naming things that are really steps. Clojure people look at that week and see a thread of map, filter, reduce, a spec at the edge, and immutability as the default so last Tuesday’s map cannot be mutated by a helper two frames down.
That is the bet. Not “Lisp is beautiful.” Not “the REPL is a lifestyle.” Those are true and insufficient. The bet is that the messy problem is a data problem, and a language that treats data as data will rot slower than a language that treats data as a class hierarchy you will be afraid to change.
What I actually like when it pays
Immutable values by default. I have hunted fewer “who changed this map” bugs in Clojure than in any equivalent Java service. The bugs I did hunt were in atoms and refs I should not have used, or in interop with a mutable Java library I wrapped too late.
The REPL as a laboratory. I still start a messy transform by throwing a real payload into a comment and walking it. That is faster than a test-first religion when I do not yet know the shape. Then I pin the cases. The REPL without the pin is a hobby. The pin without the REPL is how Java teams work. I want both.
clojure.spec or Malli at the boundary. I do not spec every function. I spec the edges: the vendor payload, the decision record we persist. When a new field arrives as a string that used to be a number, I want a failure I can name, not a NullPointerException in a helper named normalize.
The hosted story. Clojure on the JVM is how I sold it: we keep the ops we already know — metrics, thread dumps, the Kafka client — and we write the domain as values. ClojureScript exists. I have used it for an internal tool. I would not pick it to win a hiring war against React.
Datomic, when the problem is “what was true then.” I have only used it once in anger. It was the right tool for an audit-shaped domain. It was the wrong tool to introduce as a personality. Most Clojure shops I respect still use Postgres.

The costs I will not romanticize
Hiring. You can hire. You cannot hire like Java. A 100-person company that makes Clojure the default application language is making a scene into a pipeline. I will not do that. I will make Clojure a specialist island with a lid and an on-call doc the JVM people can read.
Error messages and the stack. Beginners bounce. Even seniors bounce when a macro or a lazy sequence defers the explosion. I budget time for that. I do not pretend the first month is as smooth as Spring Boot’s happy path.
Interop is the trap and the feature. The JVM library you need is there. The mutable world leaks. If half the service is wrapping Java managers, you paid the hiring tax for a thin Lisp veneer. I have seen that veneer. I have voted to rewrite the veneer in Kotlin and keep the one genuinely messy module in Clojure.
Agents are worse at Clojure than at TypeScript. The training mass is smaller. Generated Clojure looks plausible and is often unidiomatic or lazily wrong. I treat that as a reason to keep the island small and the tests honest, not as a reason the language is dead. It is a 2026 constraint. Ignore it and your “leverage” becomes a junior who cannot review the parentheses.
Performance folklore. Clojure is not slow for the messy data job. It is the wrong tool for a tight numerical kernel. I do not pick it to win a latency contest against Go. I pick it so the next rule change is a function, not a new subclass.
When I would start it
A bounded service whose job is to turn messy input into a decided output. Two or three engineers who already think in values, or one who will stay. JVM ops next door. Postgres for the durable facts. Specs at the door. A REPL habit that becomes tests before the PR.
A rules-heavy domain that product will keep extending. If the alternative is a spreadsheet that became a Java strategy pattern forest, Clojure is often cheaper over two years even if it is slower in month one.
A data transformation spine — not a toy ETL, a spine people will live in. I have watched Python notebooks become that spine and then become unowned. Clojure did better when the team treated the pipeline as the product.
When I would not
A CRUD admin. A typical B2B SaaS with twenty resources and a React front. A team of eight who interviewed as “full stack” and have never seen a Lisp. A founder who wants Clojure because of a conference feeling. A greenfield where the only complexity is that we have not written the forms yet.
I would not pick it so we look smart. Smart is a hiring filter that selects for people who enjoy being in a minority. Sometimes that minority is excellent. Sometimes it is a club. I can tell the difference in the first design review: do they talk about the payload, or about the purity of the stack?

How I would staff the island
One owner who writes the specs and the ugly tests. A second person who can land a transform without inventing a DSL. A JVM neighbor who can read a thread dump if the process misbehaves. I would not staff a rotation of tourists. Tourists write Java in Lisp and then blame the language. I would also not hide the island. The payload examples and the decision record belong in a repo the rest of the company can open. Mystery is not a feature of Lisp. It is a failure of the lid.
A messy problem I still think in Clojure
Vendor market-data files. Three formats, late arrivals, corrections that apply yesterday, a downstream that wants a single canonical quote. In Java we had started a class per vendor and a visitor. In Clojure we had maps, a spec per vendor, a merge that was a function you could replay, and a test folder of ugly files from production. When a fourth vendor showed up, we added a spec and a normalize. We did not add a type hierarchy. That week is why people still pick it.
The same company had a user-admin API in Clojure because someone had been excited. That API should have been Spring. We moved it. The market-data service stayed. Two languages, one honest reason each. That is the version I will defend.
Clojure pays when the mess is data and time and rules. It does not pay when the mess is “we have not hired a product designer.” I pick it for the first mess. I refuse it for the second. The programmers who still pick it are usually the ones who can tell those apart — and who are willing to be a smaller scene in exchange for a model that does not fight the problem.
If you are choosing this week, write down the mess. If it is vendor maps and decisions, try the island. If it is forms and auth, take the default and save the parentheses for a problem that will actually use them. I still would. I still would not start the CRUD app there, and I would not apologize to the club for saying so.
The language is not a personality. It is a bet that values and a REPL will beat a class diagram when the payload will not sit still. I have won that bet. I have also paid the hiring tax for a bet I should not have placed. Place it on purpose. That is the only way Clojure still makes sense in 2026.