Skip to content

ULID Generator — Kotlin source

Generate Universally Unique Lexicographically Sortable Identifiers (ULID) - 26-character Crockford-base32 strings that sort by millisecond timestamp. Paste any ULID to decode its timestamp and randomness. Runs entirely in your browser.

This is the Kotlin implementation — the same logic the interactive tool runs, in a shareable, citable form.

// ulid-generator - 26-char Crockford-base32 sortable ID: 10 chars of 48-bit ms timestamp + 16 chars of 80-bit randomness. Language: Kotlin (1.9+, JDK stdlib only). Port of src/lib/ulid.ts (Go twin: cli/ulid-generator) - encode/generate/decode-time core; inspectUlid (date/random-hex view) lives in this dir's javascript.js (80-line budget).
import java.security.SecureRandom

object Ulid {
    private const val CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" // no I, L, O, U
    private val rng = SecureRandom() // the JDK CSPRNG, Kotlin's crypto/rand

    // Divide the big-endian value in b (mutated in place) by 32; return the
    // remainder 0-31. Long division in base 256, exactly as in the Go twin.
    private fun divBy32(b: ByteArray): Int {
        var rem = 0
        for (i in b.indices) {
            val cur = (rem shl 8) or (b[i].toInt() and 0xFF)
            b[i] = (cur shr 5).toByte()
            rem = cur and 0x1F
        }
        return rem
    }

    // Base32-encode the 128-bit big-endian [6 time bytes | 10 random bytes] into
    // the canonical 26 chars. Because 32^16 == 2^80, the first 10 chars equal the
    // TS/Go twins' separate time encoding - the cross-language lock-step anchor.
    fun encode(timeBytes: ByteArray, randomBytes: ByteArray): String {
        val b = timeBytes + randomBytes
        val out = CharArray(26)
        for (i in 25 downTo 0) out[i] = CROCKFORD[divBy32(b)]
        return String(out)
    }

    // 48-bit ms timestamp -> 6 big-endian bytes (MSB first), like TS timeBytes().
    fun timeBytes(ms: Long): ByteArray =
        ByteArray(6) { i -> ((ms shr (8 * (5 - i))) and 0xFF).toByte() }

    // New ULID for the epoch-ms; the random tail comes from SecureRandom.
    fun generate(ms: Long): String {
        val rnd = ByteArray(10).also(rng::nextBytes)
        return encode(timeBytes(ms), rnd)
    }

    // Extract the 48-bit ms timestamp from the first 10 chars; throws on
    // malformed input (wrong length, or a char outside the alphabet such as I/L/O/U).
    fun decodeTime(id: String): Long {
        require(id.length == 26) { "ulid: malformed id: want 26 chars" }
        var ts = 0L
        for (i in 0 until 10) {
            val v = CROCKFORD.indexOf(id[i])
            require(v >= 0) { "ulid: invalid character ${id[i]}" }
            ts = ts * 32 + v
        }
        return ts
    }
}

fun main() {
    val ulid = Ulid.generate(1_722_000_000_000L)
    println("ulid:     $ulid")
    println("time(ms): ${Ulid.decodeTime(ulid)}  (round-trip ok)")
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →