MyraCodec Guide
All MVP.Express projects are currently in active development (pre-1.0.0) and should not be used in production environments. APIs may change without notice, and breaking changes are expected until each project reaches version 1.0.0. We welcome early adopters and contributors, but please use at your own risk.
MyraCodec is a schema-driven binary serialization library that generates zero-copy flyweight accessors from YAML schema definitions. It is designed for high-frequency trading and other latency-critical applications where every allocation matters.
Quick Start
Dependencies
Gradle (Kotlin DSL):
plugins {
id("express.mvp.myra-codegen") version "0.1.0-SNAPSHOT" // Optional: Gradle plugin
}
dependencies {
implementation("express.mvp.myra:myra-codec-runtime:0.1.0-SNAPSHOT")
implementation("express.mvp.roray:roray-ffm-utils:0.1.0-SNAPSHOT")
}
Maven:
express.mvp.myra
myra-codec-runtime
0.1.0-SNAPSHOT
express.mvp.roray
roray-ffm-utils
0.1.0-SNAPSHOT
JVM Arguments
MyraCodec uses Java’s Foreign Function & Memory (FFM) API:
Code Generation
Using the CLI
Generated Artifacts
For each message, the codegen produces:
{MessageName}Flyweight- Zero-copy reader with getters{MessageName}Builder- Single-pass encoder with setters
For each enum:
{EnumName}- Java enum withid()method
Lock Files
The .myra.lock file tracks:
- Stable field IDs for wire compatibility
- Schema version history
- Evolution metadata
Never delete the lock file - it ensures backward compatibility.
Binary Format
Message Layout
┌─────────────────────────────────────────────────────────────────┐
│ Message Header │
│ ┌──────────────┬──────────────┬──────────────┬───────────────┐ │
│ │ Frame Length │ Template ID │ Schema Ver │ Reserved │ │
│ │ (4 bytes) │ (2 bytes) │ (2 bytes) │ (8 bytes) │ │
│ └──────────────┴──────────────┴──────────────┴───────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Presence Bits │
│ (N bytes, where N = ceil(optional_fields / 8)) │
├─────────────────────────────────────────────────────────────────┤
│ Fixed Fields Block │
│ ┌──────────────┬──────────────┬──────────────┬───────────────┐ │
│ │ Field 1 │ Field 2 │ Field 3 │ ... │ │
│ └──────────────┴──────────────┴──────────────┴───────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Variable Fields Headers │
│ ┌──────────────────────┬──────────────────────┐ │
│ │ Offset (4 bytes) │ Length (4 bytes) │ × N fields │
│ └──────────────────────┴──────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Variable Fields Data │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Field N data ... Field N+1 data ... Field N+2 data ... │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Endianness
All multi-byte integers use big-endian (network byte order) for:
- Efficient network transmission
- Predictable cross-platform behavior
- Easier debugging with hex dumps
Fixed-Capacity String Layout
┌────────────────┬────────────────────────────────┐
│ Actual Length │ UTF-8 Data │
│ (4 bytes) │ (fixed_capacity bytes) │
│ big-endian │ (padded with zeros) │
└────────────────┴────────────────────────────────┘
Schema Evolution
Adding Fields
Safe additions:
- New optional fields at end of message
- New enum values
# Version 1
messages:
- name: "Order"
fields:
- tag: 1
name: "orderId"
type: "int64"
# Version 2 (backward compatible)
messages:
- name: "Order"
fields:
- tag: 1
name: "orderId"
type: "int64"
- tag: 2 # New field
name: "timestamp"
type: "int64"
optional: true # Must be optional for compatibility
Deprecating Fields
- tag: 5
name: "oldField"
type: "string"
deprecated: true
deprecationNote: "Use newField instead, will be removed in v3.0"
Breaking Changes (Major Version)
These require a new schema version:
- Removing fields
- Changing field types
- Changing field tags
- Making optional fields required
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
IllegalStateException: Flyweight is not wrapped | Accessing fields before wrap() | Call flyweight.wrap(segment, offset) first |
IndexOutOfBoundsException in getter | Segment too small | Check segment.byteSize() >= BLOCK_LENGTH |
IllegalStateException: Field already written | Double-setting field in builder | Each field can only be set once |
IllegalStateException: Missing required field | Not setting required field | Set all non-optional fields before build() |
| Garbled strings | Wrong encoding | Ensure UTF-8 encoding; check fixed_capacity matches data |