The libraries are used in two ways. They’re used for specifying the shape of data inside the program, for example the parameter and return types of a function. They’re also used for specifying the external interfaces of the program, for example what kind of JSON a REST API endpoint accepts. I’ll call these use cases internal and external.
- Schema was designed for internal use, but it has seen a lot of external use via libraries such as compojure-api.
- Spec was designed for internal use. For example, its support for conforming (normalization) and regular expressions over data structures are great for parsing little languages in macros and configuration.
- Malli was designed for external use. For example, the robust support for schema-driven transformations is a key feature (see e.g. my basic JSON coercion example) and the schema language of Malli is designed to be serializable.
So which one should you use? I don’t think there’s one obvious answer at the moment. Here’s how the situation looks to me:
- Schema is a proven choice, but it won’t be getting any new features. If it does what you need, great.
- Spec is the best option for the internal use case and it has a nice ecosystem of libraries such as Expound built around it. However, spec isn’t exactly easy to extend and I’m saying this as one of the authors of spec-tools. Spec’s alpha2 fixes some problems and brings new features such as select, but the development has been slow and I don’t know if there’s a good story about how we’re going to migrate the ecosystem to alpha2.
- Malli has great features for the external use case and it’s easy to build on. There’s just one downside… it has not been released yet. Go for it if you’re okay with using a library that will get some potentially-breaking changes before its first release.
Thus, it depends, but more so than usual. You have to consider your use case and your capacity for bearing technical risks.