Context
Byte-size quantities are scattered through the codebase as raw long/int fields, computed
with hand-written * 1024 arithmetic and no compiler help against a mistake (a missing
multiplier, a KiB/MiB/GiB mix-up, or a negative value all silently compile and only surface as a
wrong runtime number, if they surface at all). A few concrete examples already in the codebase:
// writer/WriteOptions.java
private static final long DEFAULT_GLOBAL_DICT_MAX_RETAINED_BYTES = 2L * 1024 * 1024 * 1024;
// reader/PostscriptParser.java
static final int MAX_LAYOUT_METADATA_BYTES = 4 * 1024 * 1024;
// performance/JniWritesJavaReadsBigFileBenchmark.java
private static final long TARGET_BYTES = 3L * 1024 * 1024 * 1024; // ~3 GB
Every one of these is a plain long/int with a unit implied only by its name and a trailing
comment — nothing stops a future edit from passing a byte count where a KiB count was expected,
or vice versa, and nothing rejects a nonsensical negative size at construction.
This follows the same "domain primitives over naked primitives" philosophy behind
Your compiler is already part of your security team,
and mirrors prior art already built (by the same author) in io.github.dfa1.zstd: ZstdByteSize
validates a byte count once at construction (rejecting negative values), exposes ofKiB(n)/
ofMiB(n) factories, and every zstd-java API that used to take a naked long/int size now
takes a ZstdByteSize instead — once you hold one, it's guaranteed valid, no more wondering
whether a raw number still needs checking.
Scope
- Add
MemorySize (or similar name — bikeshed on review) to core.model, alongside the other
typed identity/domain types (EncodingId, PType, ...). A record wrapping a validated
non-negative long byte count, with ofKiB(long)/ofMiB(long)/ofGiB(long) factories and a
raw long value()/bytes() accessor for call sites that must hand a primitive to a JDK/native
API (Arena.allocate, etc.).
- Migrate at least the two production (non-test) call sites above:
WriteOptions.globalDictMaxRetainedBytes (record component) + DEFAULT_GLOBAL_DICT_MAX_RETAINED_BYTES
PostscriptParser.MAX_LAYOUT_METADATA_BYTES
- Survey
ADR 0004 (resource caps / ReadOptions) for other byte-size caps that belong to the
same sweep, since that ADR is specifically about resource-limit constants.
Non-goals
- Not a general-purpose "units" library (no time/rate/percentage types) — scope this strictly to
byte-size quantities, matching ZstdByteSize's own narrow scope.
- Not necessarily an exhaustive one-PR migration of every byte-size
long in the codebase — the
type + the 2-3 call sites above establish the pattern; further call sites can migrate
incrementally as they're touched.
Acceptance criteria
Context
Byte-size quantities are scattered through the codebase as raw
long/intfields, computedwith hand-written
* 1024arithmetic and no compiler help against a mistake (a missingmultiplier, a KiB/MiB/GiB mix-up, or a negative value all silently compile and only surface as a
wrong runtime number, if they surface at all). A few concrete examples already in the codebase:
Every one of these is a plain
long/intwith a unit implied only by its name and a trailingcomment — nothing stops a future edit from passing a byte count where a KiB count was expected,
or vice versa, and nothing rejects a nonsensical negative size at construction.
This follows the same "domain primitives over naked primitives" philosophy behind
Your compiler is already part of your security team,
and mirrors prior art already built (by the same author) in
io.github.dfa1.zstd:ZstdByteSizevalidates a byte count once at construction (rejecting negative values), exposes
ofKiB(n)/ofMiB(n)factories, and every zstd-java API that used to take a nakedlong/intsize nowtakes a
ZstdByteSizeinstead — once you hold one, it's guaranteed valid, no more wonderingwhether a raw number still needs checking.
Scope
MemorySize(or similar name — bikeshed on review) tocore.model, alongside the othertyped identity/domain types (
EncodingId,PType, ...). A record wrapping a validatednon-negative
longbyte count, withofKiB(long)/ofMiB(long)/ofGiB(long)factories and araw
long value()/bytes()accessor for call sites that must hand a primitive to a JDK/nativeAPI (
Arena.allocate, etc.).WriteOptions.globalDictMaxRetainedBytes(record component) +DEFAULT_GLOBAL_DICT_MAX_RETAINED_BYTESPostscriptParser.MAX_LAYOUT_METADATA_BYTESADR 0004(resource caps /ReadOptions) for other byte-size caps that belong to thesame sweep, since that ADR is specifically about resource-limit constants.
Non-goals
byte-size quantities, matching
ZstdByteSize's own narrow scope.longin the codebase — thetype + the 2-3 call sites above establish the pattern; further call sites can migrate
incrementally as they're touched.
Acceptance criteria
MemorySizetype incore.modelwith full Javadoc, validated at construction(
IllegalArgumentExceptionon negative — this is a programmer-error guard oninternally-computed values, not untrusted file input, so
VortexExceptiondoesn't apply).WriteOptions/VortexWriter's global-dict retention budget migrated.PostscriptParser.MAX_LAYOUT_METADATA_BYTESmigrated.docs/reference.md's Identity types table gets a row; CLAUDE.md'score.modelmodule-mapline updated.