Skip to content

Kairo IDs

Kairo IDs are safe, meaningful, and efficient. kairo-id is an alternative to raw UUIDs or serial IDs, improving developer experience and operational clarity.

  • Semantic prefixes: IDs tell you what they represent (user_123, business_123).
  • Strong entropy: As much or more randomness than UUIDs, tunable by payload length.
  • Compile-time safety: No more accidentally swapping IDs of different entity types.
  • Zero runtime overhead: Powered by Kotlin value classes (inlined to strings).

An example Kairo ID is user_ccU4Rn4DKVjCMqt3d0oAw3.

  • Prefix: user (human-readable entity type).
  • Payload: ccU4Rn4DKVjCMqt3d0oAw3 (base-62 encoded randomness).

The entropy of Kairo IDs depends on the length of the payload portion.

ID typeEntropy
Kairo ID, length 529.8 bits (equivalent)
Integer (positive only)32 bits
Kairo ID, length 847.6 bits (equivalent)
Long (positive only)64 bits
Kairo ID, length 1589.3 bits (equivalent)
UUID (version 4)122 bits
Kairo ID, length 22131.0 bits (equivalent)
Kairo ID, length 32190.5 bits (equivalent)

Entropy calculation: length * log2(62).

  • 22: Slightly higher entropy than UUIDs.
  • 15: Good balance of entropy and readability (default).
  • 5-8: Only if a small keyspace is acceptable.

Note: like any ID scheme, Kairo IDs involve tradeoffs — generation cost, DB storage size, and index performance. Use Kairo IDs when human clarity and safety outweigh those tradeoffs.

Install kairo-id.

build.gradle.kts
dependencies {
implementation("software.airborne.kairo:kairo-id")
}

Each entity should define its own ID type — you can’t use Id directly.

@JvmInline
value class UserId(override val value: String) : Id {
init {
require(regex.matches(value)) { "Malformed user ID (value=$value)." }
}
companion object : Id.Companion<UserId>() {
val regex: Regex = regex(prefix = Regex("user"))
override fun create(payload: String): UserId =
UserId("user_$payload")
}
}

This enforces compile-time safety: IDs can’t be mixed up. Without semantic IDs, callers could accidentally swap userId and businessId. With Kairo IDs, the compiler prevents this mistake.

fun listRoles(businessId: BusinessId, userId: UserId): List<Role> {
// ...
}

Now go ahead and generate some user IDs!

UserId.random()