Codecs

ZIO-LMDB separates value serialization (LMDBCodec[T]) from key encoding (KeyCodec[K]). Both are resolved implicitly at compile time, so the API is fully type-safe with no runtime reflection.

Table of contents

  1. Value codecs — LMDBCodec[T]
    1. JSON codec (default)
    2. Custom codec
  2. Key codecs — KeyCodec[K]
    1. Built-in key codecs
    2. Specialized key codec modules
      1. keycodecs-timestamp
      2. keycodecs-uuidv7
      3. keycodecs-ulid
      4. keycodecs-geo
      5. keycodecs-uca
    3. Composite keys
    4. Custom key codec
  3. Choosing a key type

Value codecs — LMDBCodec[T]

LMDBCodec[T] encodes and decodes collection values to and from raw bytes.

trait LMDBCodec[T]:
  def encode(value: T): Array[Byte]
  def decode(valueBytes: ByteBuffer): Either[String, T]

JSON codec (default)

The zio.lmdb.json package provides LMDBCodecJson, backed by zio-json.

import zio.lmdb.json.*

case class Product(id: String, name: String, price: Double) derives LMDBCodecJson

derives LMDBCodecJson generates the JsonEncoder and JsonDecoder and wires them to LMDBCodec[Product]. No additional code required.

Custom codec

Implement LMDBCodec[T] directly to use any serialization format (Protobuf, Avro, MessagePack, etc.):

import zio.lmdb.*
import java.nio.ByteBuffer

given LMDBCodec[MyRecord] = new LMDBCodec[MyRecord]:
  def encode(value: MyRecord): Array[Byte] =
    MySerializer.toBytes(value)

  def decode(buf: ByteBuffer): Either[String, MyRecord] =
    MySerializer.fromBytes(buf.array()).left.map(_.getMessage)

Key codecs — KeyCodec[K]

KeyCodec[K] encodes and decodes collection keys. Key bytes must produce a meaningful lexicographic order because LMDB stores entries sorted by key.

trait KeyCodec[K]:
  def encode(key: K): Array[Byte]
  def decode(keyBytes: ByteBuffer): Either[KeyCodecError, K]
  def width: Option[Int] = None   // fixed byte width, if applicable

Built-in key codecs

The keycodecs module (included transitively with zio-lmdb) provides:

Key type Encoding Width Module
String UTF-8 Variable keycodecs
UUID 16-byte binary 16 bytes fixed keycodecs
(A, B) Composite (A escaped + B) Variable keycodecs

Specialized key codec modules

Add these as separate dependencies for advanced key types:

keycodecs-timestamp

Encodes java.time.Instant (or a custom timestamp wrapper) as 12 fixed bytes with nanosecond precision. 12 bytes instead of the 30+ bytes of an ISO-8601 string, with correct lexicographic ordering.

libraryDependencies += "fr.janalyse" %% "keycodecs-timestamp" % "2.8.3"
import zio.lmdb.keycodecs.timestamp.given

val events: LMDBCollection[Instant, Event] = ...

keycodecs-uuidv7

Encodes UUIDv7 (time-ordered UUID) as 16 fixed bytes. UUIDv7 keys sort chronologically, making this ideal for time-series or event-sourcing collections.

libraryDependencies += "fr.janalyse" %% "keycodecs-uuidv7" % "2.8.3"
import zio.lmdb.keycodecs.uuidv7.given
import java.util.UUID

val timeline: LMDBCollection[UUID, Event] = ...
// Keys are chronologically ordered

keycodecs-ulid

Encodes ULID as 16 fixed bytes. Like UUIDv7 but following the ULID spec.

libraryDependencies += "fr.janalyse" %% "keycodecs-ulid" % "2.8.3"
import zio.lmdb.keycodecs.ulid.given

keycodecs-geo

Encodes (latitude, longitude) pairs as 8 bytes using Morton (Z-curve) interleaving. Enables efficient spatial proximity queries via lexicographic range scans.

libraryDependencies += "fr.janalyse" %% "keycodecs-geo" % "2.8.3"
import zio.lmdb.keycodecs.geo.given

// (lat, lon) keys sorted by spatial proximity
val locations: LMDBCollection[(Double, Double), Place] = ...

keycodecs-uca

Encodes strings using the Unicode Collation Algorithm (UCA) so that lexicographic byte order matches natural language sort order for any locale.

libraryDependencies += "fr.janalyse" %% "keycodecs-uca" % "2.8.3"

Composite keys

Combine two key codecs with a tuple:

import zio.lmdb.keycodecs.given  // includes (A, B) codec

// (userId, timestamp) compound key — scan all events for a user
val userEvents: LMDBCollection[(String, Instant), Event] = ...

// Range scan: all events for "user-42"
userEvents.stream(keyFilter = _._1 == "user-42")

The composite codec correctly handles variable-width first elements by escaping the separator byte.

Custom key codec

given KeyCodec[MyKey] = new KeyCodec[MyKey]:
  def encode(key: MyKey): Array[Byte] = ???
  def decode(buf: ByteBuffer): Either[KeyCodecError, MyKey] = ???
  override def width: Option[Int] = Some(16)  // if fixed-width

Fixed-width codecs (width = Some(n)) enable LMDB’s MDB_DUPSORT optimizations for multi-value collections.


Choosing a key type

Use case Recommended key Module
Random / opaque IDs UUID keycodecs
Time-ordered IDs UUIDv7 or ULID keycodecs-uuidv7 / keycodecs-ulid
Natural string keys String keycodecs
Locale-aware string sorting UCA String keycodecs-uca
Time-series data Instant keycodecs-timestamp
Spatial data (Double, Double) keycodecs-geo
Compound keys (A, B) keycodecs

ZIO-LMDB — Copyright © 2026 David Crosson. Licensed under the Apache 2.0 License.

This site uses Just the Docs, a documentation theme for Jekyll.