Malli for Clojure: Data-Driven Schemas, Validation, and Transformation
High-performance data-driven data specification library for Clojure/Script.
At a glance
- What is it?
- Malli gives Clojure and ClojureScript applications a serializable, data-first model for validation, transformation, generation, and schema interchange. It is a strong fit for systems that load and edit data models at runtime, but its data-oriented design and schema registry choices require deliberate adoption.
- Who is it for?
- Adopt Malli when schemas must remain data that Clojure and ClojureScript can transform, persist, load, generate, or reuse across runtime boundaries. Do not choose it merely because a static Clojure API validator would be simpler.
- Can I use it commercially?
- Yes, with conditions. EPL-2.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 15 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
Malli's Data-First Model for Runtime Applications
Malli addresses a specific problem: applications that need data models to behave as first-class values rather than forms buried in macros. The README describes its origin in dynamic, multi-tenant systems where models may be edited at runtime, persisted in a database, or transferred over a wire. The same schema then needs to support Clojure on the server and ClojureScript in the browser.
The intended users are therefore teams building those runtime-configurable applications, as well as Clojure developers who want validation and transformation driven by ordinary Clojure data. A schema can drive value validation, coercion, parsing and unparsing, sample generation, documentation-oriented output, and conversions such as JSON Schema, Swagger2, DOT, and PlantUML.
That scope is broader than input validation. Malli also supports enumerations, sequences, vectors, sets, recursive schemas, multi-schemas, defaults, serializable function schemas, and several registry models. This breadth makes it relevant to shared domain models and API boundaries, but it also means adoption should not be based only on the presence of a validate function. The central question is whether schemas need to remain portable, editable data throughout their lifecycle.
Schema Evaluation, References, and Generated Values
Malli schemas are written as data. Its default vector syntax resembles Hiccup: a type such as `:string` identifies a schema, while longer vectors add properties and child schemas. The README also lists Map and Lite syntaxes, giving applications alternative representation choices without replacing the underlying schema model.
The quickstart defines a `UserId` schema, an `Address` map schema, and a recursive `User` schema. The `#'` forms are reader references rather than ordinary quoted data. They let a map property refer to an independently defined schema, while the `friends` property references `User` again through a set. That reference is significant. Recursive structures can be named without copying the complete schema into each use site.
The flow shown in the quickstart moves from schema definition to generation and then validation. `malli.generator/generate` creates a value from `User`, including an address and a set of recursive user values. The documented generator option `{:gen/max 2}` limits generated collection size. The generated result is passed to `m/validate`, which returns `true` for the conforming value.
This is more useful than a standalone random-value helper because the generated value derives from the same schema used for runtime validation. It can still be a poor fit when only fixed fixtures are needed, since Malli introduces generator behavior and schema options beyond basic validation.
Malli Installation and First Recursive Schema
The README does not provide a dependency declaration or a complete installation command. It directs readers to the Clojars project and states that Malli requires Clojure 1.11 or ClojureScript 1.11.51. The repository contains `deps.edn`, `pom.xml`, and `package.json`, but the README does not state the exact Malli coordinate syntax to place in a particular application. This tutorial therefore starts with the documented quickstart rather than inventing an installation step.
Start a Clojure environment that meets the stated runtime requirement, load the namespaces used by the quickstart, and define the schemas. The `#'UserId` and `#'Address` references connect reusable definitions to the final map.
(require '[malli.core :as m])
(def UserId :string)
(def Address
[:map
[:street :string]
[:country [:enum "FI" "UA"]]])
(def User
[:map
[:id #'UserId]
[:address #'Address]
[:friends [:set {:gen/max 2} [:ref #'User]]]])Next, require the generator namespace and call `generate` with `User`. The exact generated strings and recursive values are not fixed because generation can produce different data. The README's quickstart produces a map whose `friends` entry is a set.
(require '[malli.generator :as mg])
(mg/generate User)Pass the generated value to `m/validate`. A conforming generated value should produce `true`.
(m/validate User *1)
; => trueRegistries, Branching, and Portable Function Schemas
Malli offers immutable, mutable, dynamic, lazy, and local schema registries. This is a meaningful design choice for the dynamic systems described in the README. An application can keep schemas immutable, maintain mutable definitions, bind dynamic schemas, defer lookup, or use a local registry. The README does not provide a recommendation for selecting among them, so the correct choice depends on where schemas should live and when they should change.
This registry model also addresses a weakness the project identifies in Schema. Malli says serializing and deserializing Schema definitions is non-trivial and that branching support is inadequate for its needs. By representing schemas as data, Malli can persist them and support serializable function schemas. It also provides function schemas with dynamic and static checking, including integration points named for `clj-kondo` and Typed Clojure.
The limitation is equally concrete. Portable schemas are not automatically a complete cross-platform domain model. Malli targets Clojure and ClojureScript, while conversions such as JSON Schema or Swagger2 expose selected schema concepts to other systems. Features such as function schemas and registry behavior remain part of the Clojure-facing ecosystem. Review the documentation for each output format before assuming a round trip preserves every Malli-specific capability.
Transformation, Parsing, and Documentation Output
Malli treats schemas as operational definitions rather than static type declarations. The documented capabilities include value transformation, parsing, unparsing, error messages, spelling checks, and default values. A schema can therefore describe more than acceptable shape. It can supply the rules used to turn input into a working value or report structured failure information.
The library also transforms schemas into representations used outside the running program. JSON Schema and Swagger2 are explicit targets. Error messages are first-class, including spell checking, which can help users diagnose misspelled schema keys. The README further lists DOT and PlantUML visualization.
These exports create a useful separation between internal execution and external consumption. A Clojure application can validate with a native Malli schema while documentation or another service receives JSON Schema. The trade-off is fidelity. The README identifies supported transformations but does not claim lossless conversion among Malli, JSON Schema, Swagger2, and visualization formats. Malli-specific references, registries, transformations, or function schemas may need representation decisions outside its core vocabulary. JSON Schema conversion should therefore be checked against the exact schema features used by the application.
Limitations and the Alternative Clojure Libraries
Malli is the wrong tool when a project needs only straightforward local validation and has no reason to treat schemas as editable, portable data. Its runtime, registry, transformation, generation, and export capabilities can add conceptual surface that a small validation function does not require. The README labels the project a well-matured alpha and requires recent Clojure or ClojureScript versions, so teams constrained by older runtimes must account for that requirement.
The README names Schema as a proven, collaborative alternative that Metosin still uses. The documented distinction is that serializing and deserializing Schema definitions is non-trivial, while Malli is designed around schemas as data with branching support. A team already standardized on Schema may reasonably stay there if editability and persistence are not requirements.
`clojure.spec` is another documented comparison. The README characterizes Spec as opinionated, macro-based, and dependent on a global registry, with no support for runtime transformations. Malli instead offers data definitions, several registry types, transformations, and schema interchange. Spec may still fit an application centered on Clojure's standard specification workflow. Malli fits when runtime model control is itself part of the system design.
Malli's Release State and Upgrade Surface
The repository is not archived, and its last push was on 2026-10-08. Releases 0.20.2 and 0.20.3 were published on 2026-09-25 and 2026-10-08 respectively, so the project has recent release activity. The README labels Malli active and describes its stability as a well-matured alpha, but that wording does not promise compatibility across every future release.
Malli is distributed under EPL-2.0. The README and repository identify the license, while the project documentation does not state additional application obligations. Organizations with formal licensing policies still need to review EPL-2.0 against their distribution and linked-library arrangements.
Upgrading may matter because the project offers a broad API across schema syntax, registries, transformations, generators, function schemas, and format exports. The release tags show patch-level publication, but they do not document upgrade commands or compatibility guarantees here. A consuming team should check `CHANGELOG.md` and its own pinned dependency configuration when moving between versions, especially if it relies on persisted schemas.
Editorial conclusion
Adopt Malli when schemas must remain data that Clojure and ClojureScript can transform, persist, load, generate, or reuse across runtime boundaries. Do not choose it merely because a static Clojure API validator would be simpler. Verify first that the required Clojure 1.11 or ClojureScript 1.11.51 runtime is acceptable, then inspect how your application will configure dependency resolution and whether schema registries should remain local, dynamic, or globally shared.
Frequently asked questions
What are Malli's runtime requirements?
Malli requires Clojure 1.11 or ClojureScript 1.11.51. The README also says it is tested with Java 8, 11, 17, 21, and 25 LTS releases.
Can Malli represent recursive schemas?
Yes. The quickstart defines a `User` map whose `friends` set contains references back to `User`, demonstrating a recursive schema.
How does Malli differ from clojure.spec?
The README says `clojure.spec` is macro-based, opinionated, tied to a global registry, and lacks runtime transformations. Malli uses data-driven schemas, offers several registry types, and supports value transformations and schema conversion targets such as JSON Schema and Swagger2.
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/metosin-malli)Was this useful?