konform: type-safe validation DSL for Kotlin, on every platform
Portable validations for Kotlin
At a glance
- What is it?
- Konform gives you a validation DSL that keys off property references rather than strings, runs on JVM, JS, Native and Wasm, and ships with no dependencies. The cost is a DSL you have to learn, and a release history that has not moved much since March 2025.
- Who is it for?
- Adopt konform if you write Kotlin and want validation rules attached to property references so that a renamed field breaks the build rather than the production request. Do not adopt it if your team would rather keep validations as plain data or as runtime checks, since the DSL is a learning cost that only pays off if it is used consistently.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly Kotlin, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The DSL keys off property references, not strings
Most validation libraries for statically typed languages want you to name the field twice, once in a string and once in code, and the two can drift. Konform is built to avoid that. The rules are attached to property references, so the compiler sees them. Given a data class:
data class UserProfile(
val fullName: String,
val age: Int?
)the validation for it is written as:
val validateUser = Validation<UserProfile> {
UserProfile::fullName {
minLength(2)
maxLength(100)
}
UserProfile::age ifPresent {
minimum(0)
maximum(150)
}
}The `UserProfile::fullName` form is a property reference, so renaming the property in the data class turns every rule attached to it into a compile error rather than a string that no longer matches anything. The second block shows the other half of the idea. The `ifPresent` block applies only when the value is not null, which is how a nullable Int gets range checked without forcing you to handle a null case separately. The library advertises three headline properties, a type-safe DSL, multi-platform support across JVM, JS, Native and Wasm, and zero dependencies.
Adding the dependency to a multiplatform project
For a multiplatform project the dependency goes into the common source set, which is what makes the validation code shared rather than duplicated per target:
kotlin {
sourceSets {
commonMain {
dependencies {
implementation("io.konform:konform:0.11.0")
}
}
}
}The coordinate is io.konform:konform, published on Maven Central, and the version pinned in the README example is 0.11.0. For a JVM-only project the artifact is different:
dependencies {
implementation("io.konform:konform-jvm:0.11.0")
}That suffix is worth noting. The common artifact is what carries the shared API, while konform-jvm is the JVM-specific variant, so a project that starts on the JVM and later adds a second target needs the coordinate revisited rather than simply kept. The repository is a Gradle project with build.gradle.kts, settings.gradle.kts, a gradlew wrapper, a kotlin-js-store directory for the JavaScript target, and an api/ directory that appears to hold the published API dump. There is also a PUBLISHING.md at the root and a CHANGELOG-equivalent release workflow badge pointing at release.yml.
A result type, not a thrown exception
Konform returns a result rather than raising. Calling the validation gives you a `Valid` holding the validated value in its `value` field, or an `Invalid` carrying the errors. The README makes the distinction visible with an example: `UserProfile("Alice", 25)` yields `Valid(UserProfile("Alice", 25))`, while `UserProfile("A", -1)` yields an `Invalid`. Errors are reachable per property rather than only as a flat list. The call `validationResult.errors.messagesAtPath(UserProfile::fullName)` yields a list containing the message must have at least 2 characters, and the same call for the age property yields must be at least '0'. You can also take the whole set as a list of ValidationError objects, each with a path and a message. The path is itself structured, a `ValidationPath` wrapping a `Prop(fullName)`, which is what makes the per-property access type-safe rather than string-keyed. This is the design choice that matters most in practice: a validation failure becomes data you can branch on, log and serialise, not an exception you have to catch.
Hints let you replace the default message
The default messages are terse on purpose, minimum and maximum style constraints, which is fine for a machine but not for a user. Konform lets you attach a hint to any constraint, and the hint can interpolate the value. The simple form:
val validateUser = Validation<UserProfile> {
UserProfile::age ifPresent {
minimum(0) hint "Registering before birth is not supported"
}
}and the version that includes the offending value:
val validateUser = Validation<UserProfile> {
UserProfile::fullName {
minLength(2) hint "'{value}' is too short a name, must be at least 2 characters long."
}
}The `{value}` placeholder is replaced with the string form of the data being validated, which turns a generic constraint failure into a message that names the input. That matters in a form-validation context, where the error usually has to go back next to the field. There is a second axis here as well. A rule can carry a user context, and the README shows minimum(0) userContext Severity.ERROR alongside a replace form that sets hint and userContext together, so a rule can declare both the message and the severity in one call. The combination is what lets a caller distinguish a warning from a blocking error without re-reading the rule definitions.
Constrain for rules the library does not have
When the built-in constraints do not cover a case, `constrain` takes a message and a lambda. The examples show the three things you can attach to it. The message, as in a rule that a name cannot contain a tab. A custom path, so the error is reported against a different name than the property it was read from, using ValidationPath.of("trimmedName"). And a user context, so the rule can carry Severity.ERROR like a built-in one. The shape is short:
val validateUser = Validation<UserProfile> {
UserProfile::fullName {
constrain("Name cannot contain a tab") { !it.contains("\t") }
}
}Konform also lets you transform data and then validate the result, through a `validate` call that takes a name and a transformation and then applies rules to the output. There is a matching `required` form for nullable values, which fails if the result is null, and an `ifPresent` form which only runs when it is not null. The path can be a compound one, so a validation on a trimmed name reports at a path of .fullName.trimmed. This is the part of the library that goes beyond validation and into a small data pipeline, and it is where the library earns its keep for form-shaped data.
Split and reuse rules across objects and collections
Rules can be defined once and run from elsewhere. The README defines an ageCheck validation over an Int? with a required block that applies minimum(21), then runs it from a user validation with a run call, and also runs it against a transformed value with an ageMinus10 name whose path is built by subtracting ten from the age. Beyond reuse, the library handles nested and collection-shaped data. A validation can target a nested data class property, can require a nullable field to be present, and can apply onEach to every element of a List or Map. For a Map of nullable ticket prices, the per-entry rule is written against Entry<String, Double?> and its value property, with an ifPresent so that a null entry, a free ticket, is skipped rather than failed. Errors in a collection are then addressed by index for Iterables and Arrays, and by key string for Maps. The README does not document the per-element path format, so if your errors need to point at a specific list entry in a form, that is a question to check before you commit to the library.
MIT licensed, multiplatform, and quiet between releases
The licence is MIT, with the LICENSE file at the repository root, and the project publishes to Maven Central under the io.konform group. The topics are kotlin, kotlin-multiplatform and validation, which is an accurate summary of what this is. The release history is the part to look at with care. The listed releases are v0.10.0 on 2024-11-17 with the note that it brings a dynamic context, v0.11.0 on 2025-03-21, and v0.11.1 on 2025-03-31. The repository is not archived and the last push was on 2026-10-14, so work is landing on the branch, but there has been no tagged release since March 2025. For a library that other people's builds depend on, that gap means two things: the artifact you get from Maven Central is 0.11.x, and anything newer exists only as a commit. The repository also contains a CLAUDE.md file, a renovate.json for dependency updates, a CNAME and _config.yml for the project site at konform.io, and a CNAME alongside it, which is a documentation site hosted separately from the code.
Editorial conclusion
Adopt konform if you write Kotlin and want validation rules attached to property references so that a renamed field breaks the build rather than the production request. Do not adopt it if your team would rather keep validations as plain data or as runtime checks, since the DSL is a learning cost that only pays off if it is used consistently. Before you commit, confirm your Kotlin Multiplatform targets are covered by the io.konform:konform artefact rather than the jvm-only one, read PUBLISHING.md in the repository for the release process, and note that the newest release is v0.11.1 from 2025-03-31 while the last push was on 2026-10-14.
Frequently asked questions
What is konform?
It is an MIT licensed Kotlin library for portable validations, with a type-safe DSL, multi-platform support for JVM, JS, Native and Wasm, and zero dependencies. It is published on Maven Central as io.konform:konform.
How do I add konform to a Kotlin project?
In a multiplatform project, add implementation("io.konform:konform:0.11.0") inside the commonMain source set dependencies. For a JVM-only project the coordinate is the JVM variant, implementation("io.konform:konform-jvm:0.11.0").
Does konform throw on a validation failure?
No. The validation returns a result that is either Valid with the value in its value field, or Invalid carrying the errors. Errors can be read per property with messagesAtPath using a property reference, or taken as a list of ValidationError objects with a path and a message.
How do I write a validation rule that konform does not provide?
Use constrain, which takes a message and a lambda over the value. You can also set a custom path for the error with a ValidationPath argument and attach a user context such as Severity.ERROR, and there is a replace form that sets hint and userContext together.
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/konform-kt-konform)Was this useful?