A generic Value Object library for Kotlin. Inspired by Zod. We never use 3rd party libraries.
A Value Object is an object whose equality is determined by the "values of its attributes" rather than the object's "identifier." In Domain-Driven Design (DDD), they are used to clearly model concepts and rules.
iolite is published as a Kotlin Multiplatform library and runs on:
- JVM (Java 1.8+)
- Kotlin/JS (IR, browser & Node.js)
- Kotlin/Native — Linux x64
- Kotlin/Native — macOS x64 / arm64
- Kotlin/Native — iOS arm64 / simulator arm64 / x64
view iolite API
- Date
- DateTime
- Time
- Base64
- UUID
- CIDR
- Domain
- HostName
- IPV4
- IPV6
- MacAddress
- URL
- Age
- CreditCardNumber
- JP Phone Number
- JP Postal Code
- AlphaNumeric
- Decimal
- Integer
val email = Email("youremail@example.com")
You will primarily be using parse and safeParse, which are explained in more detail below.
Parse validates the input value provided and raises an exception if it is invalid.
try {
val email: String = Email("youremail@example.com").parse()
}catch (e: IoliteException) {
// Handle the exception if needed
}
safeParse also verifies that the given value is valid, but returns a Result type instead of an exception.
val email = Email("youremail@example.com").safeParse()
if(email.isFailure){
// Handle the exception if needed
}
println(email.getOrNull()) // print "youremail@example.com"
iolite follows the Kotlin API guidelines for debuggability and throws a single library-specific exception type for every validation failure.
- Every
parse()call raisesiolite.IoliteException(and only that type) when validation fails. safeParse()catches onlyIoliteExceptionand converts it intoResult.failure. Any other exception thrown from insideparse()(e.g. a bug in a custom validation lambda) is intentionally not swallowed, so genuine bugs are not hidden as validation failures.IoliteExceptionextendsIllegalArgumentExceptionfor source compatibility — existingcatch (e: IllegalArgumentException)blocks keep working — but new code SHOULD catchIoliteExceptionto distinguish iolite validation failures from other argument errors.- Each exception carries structured fields so failures can be inspected without parsing message strings:
target: IoliteException.Target— which Value Object failed (e.g.Email,CreditCardNumber).rule: IoliteException.Rule— which rule was violated (e.g.Format,Range,Luhn).
import iolite.IoliteException
import iolite.personal.Email
val result = Email("not-an-email").safeParse()
result.exceptionOrNull()?.let { e ->
if (e is IoliteException) {
println("validation failed: target=${e.target} rule=${e.rule}")
// -> validation failed: target=Email rule=Format
}
}All value classes other than StringValueObject in the strings package inherit StringValueObject, and it is possible to check the values using method chaining as shown below.
val stringVal = StringValueObject("prefix123suffix")
.notEmpty()
.startWith("prefix")
.endWith("suffix")
.min(10)
.max(20)
.regex(Regex("^[a-zA-Z0-9]+$"))
.customerValidation(
validation = { it.contains("123") },
errorMessage = "Custom validation failed"
)
.parse()
val integerString = IntegerString("100000000")
.parse() // can retlieve StringValueObject
.notEmpty()
.min(3)
.max(10)
.parse()
You can validate these credit card brands.
- Amex
- Diners Club
- Discover
- JCB
- MasterCard
- Visa
val creditCardNumber = CreditCard("4111111111111111").parse()
this is same logic of Valibot. and By running them through the same test cases, we can ensure that they are of the same quality.
implementation("io.github.ysknsid25.iolite:iolite:{version}")kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.ysknsid25.iolite:iolite:{version}")
}
}
}refer here
You can use either IntelliJ IDEA (the project's primary IDE) or VSCode with a Dev Container.
Prerequisites:
- Docker Desktop (or Docker Engine on Linux)
- VSCode with the Dev Containers extension
Steps:
-
Clone the repository and open the folder in VSCode.
-
When prompted, choose "Reopen in Container" (or run the
Dev Containers: Reopen in Containercommand). -
The container builds automatically. On first start it installs Claude Code, configures the git hooks, and warms up the Gradle cache.
-
From a container terminal, you can run the same tasks CI runs:
./gradlew detekt # static analysis ./gradlew allTests # common + JVM tests ./gradlew koverXmlReport # coverage report
What the container provides:
- Temurin JDK 17 (LTS) with Gradle wrapper-driven builds
- Pre-configured VSCode extensions: Kotlin (
fwcd.kotlin), Gradle, Java test runners, YAML, EditorConfig, and Claude Code - Claude Code CLI installed via the official
curl -fsSL https://claude.ai/install.sh | bashscript. Your host~/.claudeand~/.claude.jsonare bind-mounted into the container so credentials, settings, and history are shared with your host setup. - Auto-configured
pre-commitgit hook (.githooks/pre-commitactivated viacore.hooksPath)
Open the project folder in IntelliJ — Gradle import runs automatically. After import, enable the pre-commit hook:
chmod +x .githooks/pre-commit
git config core.hooksPath .githooksAll tasks are run via the bundled Gradle wrapper (./gradlew) regardless of whether you use IntelliJ or a Dev Container.
./gradlew build # full build (compile + test + check)
./gradlew clean # remove build artifacts
./gradlew assemble # compile only, skip tests./gradlew allTests # run all KMP tests (commonTest + jvmTest)
./gradlew jvmTest # run JVM tests only (JUnit 5 + Konsist)
./gradlew check # run all verification tasks (test + detekt)./gradlew detekt # run Detekt with auto-correct enabled
# report: reports/detekt.htmlDetekt configuration: config/detekt/detekt.yml.
./gradlew koverHtmlReport # generate HTML coverage report
# report: build/reports/kover/html/index.html
./gradlew koverXmlReport # generate XML coverage report (used by CI)
# report: build/reports/kover/report.xml
./gradlew koverVerify # fail the build if coverage is below the threshold./gradlew dokkaHtml # generate API docs into docs/./gradlew tasks # list all available tasks
./gradlew help --task <name> # show details for a specific task- Inspired by Zod. Zod's icon is a blue gem, so I thought I'd use the gem's name.
- I thought of jewels with similar colors to Kotlin's brand color.
- Because my favorite artist has a song with the same name.