typedclojure: an optional type system for Clojure that splits at build time
An optional type system for Clojure
At a glance
- What is it?
- Typed Clojure is a static type checker for Clojure delivered as a library rather than a compiler fork, and its defining design choice is the split between a dev-time checker module and a small runtime module that ships to production. It fits a Clojure team that wants type information without paying for it at runtime, and it fits poorly if your team wants types to be mandatory everywhere.
- Who is it for?
- Typed Clojure fits a Clojure team that wants optional static checking on the parts of the codebase that need it, and that can keep the checker out of the production artefact, which the zero-deps example project shows how to do. It is the wrong choice for a team that wants types enforced on every namespace from day one, and the wrong choice for a codebase that cannot absorb the cost of annotating existing code.
- Can I use it commercially?
- Yes, with conditions. EPL-1.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 55 days ago.
- What is it written in?
- Mainly Clojure, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A type checker as a library, not a fork of the compiler
Typed Clojure is described in one line as optional typing in Clojure, as a library, and the project description calls it an optional type system for Clojure.
The library framing is the whole point. A compiler fork that adds types changes the toolchain everyone uses. A library that you depend on at development time and that your production artefact does not depend on changes nothing about how your code runs. The README is explicit that Typed Clojure is separated into modules, and that you want the full type checker at development time and the runtime dependencies in production.
The concrete version of that split is in the dependency coordinates. The runtime module is org.typedclojure/typed.clj.runtime, and the checker is org.typedclojure/typed.clj.checker, which lives in a dev alias rather than in the main dependency set. The latest stable release is 1.3.0, and both modules in the example use that same version.
The consequence worth stating plainly: types in this system are a development-time artifact. The checker runs, it tells you something is wrong, and then it is not there at runtime. That is a different philosophy from a language with types in its runtime representation, and it is why the annotations live in metadata and separate annotation libraries rather than in the function signatures.
The audience is a Clojure team that wants type information on some of its code without a rewrite. The project homepage is typedclojure.org, the licence is EPL-1.0, and the last push to the repository was on 2026-08-29 with the repository not archived.
The Clojure CLI and Leiningen setup, and the uberjar check
The usage section gives both build tool configurations, and they are worth reading side by side because the shape is the same in each: runtime in the main dependencies, checker in a dev profile.
In the Clojure CLI's deps.edn it looks like this:
{:deps {org.typedclojure/typed.clj.runtime {:mvn/version "1.3.0"}}
:aliases {:dev {:extra-deps {org.typedclojure/typed.clj.checker {:mvn/version "1.3.0"}}}}}You then start a development repl with clj -A:dev.
In Leiningen's project.clj the same split appears as a dev profile:
(defproject com.my-domain/a-project "1.0.0-SNAPSHOT"
:dependencies [[org.typedclojure/typed.clj.runtime "1.3.0"]]
:profiles {:dev {:dependencies [[org.typedclojure/typed.clj.checker "1.3.0"]]}})And in that case lein repl automatically activates the :dev profile.
The detail that shows the project is serious about its own claim comes next. The README says to verify the type checker is not included in uberjars with lein with-profile uberjar deps :tree. That is a concrete, checkable instruction, and it matters because the whole promise of the module split is that the checker does not reach production. If it did, the cost of the tool would land on every deployment for no benefit.
The same separation appears as a first-class example project. The zero-deps example demonstrates how to type check a library without introducing any runtime dependency on Typed Clojure, only a dev-time one. If that example works, the architecture holds; if you skip it, you are shipping a type checker to production and the design has quietly failed.
The module map, and what each piece actually does
The releases and dependency information section lists the modules by category, and the categories are a good description of the architecture.
The Clojure implementation group has typed.clj.checker, the JVM type checker, and typed.clj.runtime, the JVM runtime dependencies. Alongside them sit typed.clj.analyzer, described as the analyzer for JVM Clojure, and typed.malli, a Malli integration.
The implementation-agnostic group has typed.cljc.analyzer, described as the implementation-agnostic base for Clojure analyzers. That single module is what makes the rest of the design possible, because the checker needs to analyse code without being tied to one dialect, and a library that only works on the JVM cannot be used to check a multiplatform project.
The library annotations group is where the type information about other libraries lives: typed.lib.clojure as the base type annotations, typed.lib.core.async for annotations for core.async, and typed.lib.spec.alpha for annotations for spec.alpha. Those three cover the two most common sources of type friction in a Clojure codebase, a concurrency library and a schema library, and annotating them is a large amount of work that the project has done on your behalf.
The analyzer modules are the part with an interesting history. The licence section at the end of the README lists separate copyright blocks for the project itself and for tools.analyzer, tools.analyzer.jvm and tools.analyzer.js, each with Nicola Mometto and Rich Hickey as copyright holders. The analyzer used to live inside the type checker and now sits as a separate layer with a JVM and a JavaScript implementation, which is the same separation of concerns the rest of the project follows.
The contributor list is long and includes Rich Hickey, Ambrose Bonnaire-Sergeant and Nicola Mometto, which tells you the lineage is the original core.typed project, renamed.
The example projects, which are the real documentation
Five example projects are listed, and each one is a different answer to a different question a team will have.
The minimal project demonstrates how to statically type check Clojure namespaces, and the minimal-clj project is described in the same terms, which is worth noticing because the two of them exist separately. The malli-type-providers project demonstrates how to statically type check using malli schemas, which is the path for a codebase that already declares its data shapes with Malli and does not want to declare them twice.
The zero-deps project, discussed above, demonstrates how to type check a library without introducing any runtime dependency on Typed Clojure. And the symbolic-guide project shows how to reason about symbolic execution in Typed Clojure, which is a different matter entirely: it is about what the type system can prove about a value rather than what shape it has.
The README calls the minimal example the place to look for Typed Clojure setup, which is a reasonable starting order: get the wiring right on a tiny project, then look at the variants.
For documentation, the README points at the wiki of the core.typed project, an API reference for the runtime module hosted at api.typedclojure.org, and a blog by Ambrose, and the project has a website directory in the repository along with a doc directory and a design directory. The BIDIRECTIONAL_DOC_TESTING.md file in the repository root suggests that the documentation examples are themselves tested, which is a good sign for a project whose documentation is full of code.
The support channels are a core.typed mailing list on Google Groups and the core-typed channel on Clojurians Slack, with a Slack archive on Zulip.
Where typedclojure is the wrong tool
The first wrong-tool case is a team that wants types to be mandatory. The project is called an optional type system, and every piece of the design follows from that word. If your requirement is that an unannotated namespace fails the build, this is not the tool, and no amount of configuration changes it.
The second is an existing large codebase with no annotations. Type checking in Clojure works from annotations, and a namespace with no annotations is a namespace the checker knows little about. The zero-deps example demonstrates the technique on a library; retrofitting hundreds of namespaces is a project in itself, and the README's own framing, that this is not a course on how to write Clojure programs, is a hint that the learning curve is real.
The third is a team that wants runtime type enforcement. Because the runtime module is deliberately small and the checker is a dev-time dependency, nothing checks a value at runtime. A system that must reject bad data coming in from a service needs either a schema library or a runtime-checked contract, and the malli integration is the bridge for teams who already think in Malli.
The fourth is a multiplatform project where the JVM is not the target. The support statement is that Typed Clojure supports Clojure 1.12.5 and JDK 21+, which is a JVM statement. The analyzer layer is implementation-agnostic and there is a JavaScript analyzer, so the design has room for other targets, but the compatibility guarantee in the README is about the JVM, and that is what you should plan against.
The fifth is a team that needs a stable API for a build pipeline. The latest stable release is 1.3.0, the repository has no GitHub releases, and the root of the repository contains an UPGRADING.md file, a next-release-changes.md file and a stable-sha and stable-version file. Those are the files of a project whose internals move, and a continuous integration pipeline that depends on the checker needs to know which version it is on.
Licensing, governance and who maintains it
The licence section is unusually granular, and the granularity tells you something about the structure.
The project itself is under the EPL, with the file named epl-v10.html, and the copyright is held by Ambrose Bonnaire-Sergeant, Rich Hickey and contributors. The tools.analyzer project, and its jvm and js variants, carry their own copyright blocks with Nicola Mometto, Rich Hickey and contributors, each under the same EPL terms.
So the whole distribution is EPL, with the analyzer subproject tracked separately for attribution purposes rather than under a different licence. That is a file-level weak copyleft, and for a type checker that ships as a dev-time dependency the practical effect on a commercial product is small, but it is a licence and the README does not ask you to take it on trust.
The funding model is visible in the README header, with a Patreon button for Ambrose and an OpenCollective link for Typed Clojure. Volunteer and patron funding for a type checker is a differentiator in the Clojure ecosystem, and it is also a signal about the project's shape: it is maintained by a small group around its original authors rather than by a company with a support contract behind it.
The governance files are standard. There is a Typed Clojure Contributor Code of Conduct and a Contributing guidelines file, and the repository root carries a CONTRIBUTING.md, a CHANGELOG.md, an UPGRADING.md, a dependabot directory for dependency updates, a script-test directory, and a dev directory holding the resources that the README says to edit, since the README itself is generated from a template through a regen script.
That last detail is small but telling: the README opens with a note not to edit it directly, but to edit the template in dev/resources/root-templates and run the regen script. Documentation is generated here, not hand-maintained.
Editorial conclusion
Typed Clojure fits a Clojure team that wants optional static checking on the parts of the codebase that need it, and that can keep the checker out of the production artefact, which the zero-deps example project shows how to do. It is the wrong choice for a team that wants types enforced on every namespace from day one, and the wrong choice for a codebase that cannot absorb the cost of annotating existing code. Before adopting it, read the minimal example project to see what an annotated namespace looks like, and check the UPGRADING.md file, because the checker is a moving part and the migration notes are where the breakage shows up.
Frequently asked questions
What is Typed Clojure and how is it delivered?
It is an optional type system for Clojure delivered as a library, split into modules so that the full type checker is a development-time dependency while only the small runtime module is needed in production.
How do I add Typed Clojure to a Clojure CLI project?
Add org.typedclojure/typed.clj.runtime at version 1.3.0 to the deps map and org.typedclojure/typed.clj.checker at the same version in a dev alias, then start a development repl with clj -A:dev.
How do I make sure the type checker does not end up in my production artefact?
The README says to verify it with lein with-profile uberjar deps :tree, and the zero-deps example project demonstrates type checking a library with only a dev-time dependency on Typed Clojure.
Which Clojure and JDK versions does Typed Clojure support?
The README states that Typed Clojure supports Clojure 1.12.5 and JDK 21 or newer, and that the latest stable release is 1.3.0.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/typedclojure-typedclojure)Was this useful?