Skip to main content

Record Streaming

The RecordReader / RecordWriter surface yields one whole-value record per call. It works uniformly across every Juneau format that implements the RecordReadable / RecordWritable role.

See the per-format capability matrix for which formats support record reading/writing and whether the cursor streams O(1) or buffers.

Reading records​

try (RecordReader r = JsonParser.DEFAULT.readRecords(input)) {
while (r.canRead()) {
Bean bean = r.read(Bean.class);
process(bean);
}
}

For single-rooted formats (json, xml, bson, yaml, ...), the cursor yields exactly one record (the root value), then exhausts. For naturally multi-rooted formats (jsonl, csv, sse), the cursor yields N records — one per line, row, or event on the wire.

Writing records​

try (RecordWriter w = JsonSerializer.DEFAULT.writeRecords(output)) {
w.write(bean);
}

For single-rooted formats, exactly one write(...) call is allowed; subsequent calls throw. For multi-rooted formats, N calls produce N records on the wire — wire-equivalent to serializer.write(List.of(records)).

Iterator and Stream views​

Convenient JDK-friendly views on top of read(Class):

try (RecordReader r = JsonlParser.DEFAULT.readRecords(input)) {
for (Bean b : (Iterable<Bean>) () -> r.iterator(Bean.class)) {
process(b);
}
}

try (RecordReader r = JsonlParser.DEFAULT.readRecords(input)) {
int totalAge = r.stream(Bean.class).mapToInt(b -> b.age).sum();
}

Equivalence with parse(...)​

For single-rooted formats, parser.read(input, T) and parser.readRecords(input).read(T) produce identical results. For multi-rooted formats, readRecords(input).read(T) returns the FIRST record while parse(input, T) may return the first or all of them depending on T:

// JSONL: 3 records on the wire.
String wire = "{\"n\":\"a\"}\n{\"n\":\"b\"}\n{\"n\":\"c\"}";

Bean first = JsonlParser.DEFAULT.read(wire, Bean.class); // returns Bean("a")

try (RecordReader r = JsonlParser.DEFAULT.readRecords(wire)) {
Bean a = r.read(Bean.class); // first
Bean b = r.read(Bean.class); // second
Bean c = r.read(Bean.class); // third
}

@SuppressWarnings("unchecked")
List<Bean> all = (List<Bean>) JsonlParser.DEFAULT.read(wire, List.class, Bean.class);

Capability check​

if (parser instanceof RecordReadable p) {
try (RecordReader r = p.readRecords(input)) {
// ...
}
}

Format-specific notes​

  • CSV / JSONL / SSE — naturally row/line/event streamed; readRecords is the multi-record cursor.
  • JsonSchema / JCS / SOAP-XML — writer-only; writeRecords(output).write(value) produces the schema / canonical / SOAP-wrapped output. No readRecords.
  • PlainText — only round-trips strings via toString(). readRecords(input).read(String.class) returns the body verbatim.
  • CBOR / MsgPack / BSON / Prototext / Parquet — binary; pass an OutputStream / byte[] / InputStream instead of a Writer / Reader.

See also​