Skip to main content

REST Streaming Integration

TokenReader, TokenWriter, RecordReader, and RecordWriter are first-class @RestOp-method parameter types. Declare one in your method signature and the framework picks the matching parser/serializer based on the request Content-Type / response Accept and hands you the cursor.

See the per-format capability matrix for which formats can satisfy a RecordReader / TokenReader / RecordWriter / TokenWriter parameter.

Server-side reader parameter​

@Rest(parsers = JsonParser.class)
public class MyResource {

@RestPost
public String process(RecordReader r) throws Exception {
Bean b = r.read(Bean.class);
return b.name;
}
}

Concrete cursor types work too — declaring JsonlTokenReader means only JsonlParser is eligible:

@Rest(parsers = JsonlParser.class, defaultAccept = "application/jsonl")
public class JsonlIngest {

@RestPost
public int countRecords(JsonlTokenReader r) throws Exception {
int n = 0;
while (r.canRead()) {
r.read(Bean.class);
n++;
}
return n;
}
}

Server-side writer parameter​

Declaring a writer parameter flips the response into "user owns the body" mode — the framework will not later serialize a return value over the same stream:

@RestGet
public void exportLargeArray(RecordWriter w) throws IOException {
for (Bean b : sourceOfBeans()) {
w.write(b);
}
// close() is the framework's responsibility on return.
}

Two-layer resolution​

Cursor parameter resolution uses two layers:

  1. Declared type filters eligible parsers/serializers. A RecordReader parameter accepts any parser that is a RecordReadable; a TokenReader parameter accepts only parsers that are a TokenReadable; a concrete JsonlTokenReader parameter accepts only parsers whose readTokens(...) returns a JsonlTokenReader. Capability is detected with instanceof against the role interface — there is no capability enum.
  2. HTTP Content-Type / Accept negotiates among the eligible set. Standard Juneau media-type matching applies.

Mismatch returns HTTP 415 Unsupported Media Type (or 406 Not Acceptable on the writer side):

  • The matched parser/serializer does not implement the required role interface (RecordReadable / TokenReadable / ArrayRecordReadable and their *Writable mirrors).
  • The matched cursor type isn't assignable to the declared parameter.
  • No parser/serializer matched the requested media type at all.

Multi-parser example​

@Rest(parsers = {JsonParser.class, CborParser.class}, defaultAccept = "application/json")
public class MultiFormat {

@RestPost
public String process(TokenReader r) throws Exception {
Bean b = r.read(Bean.class);
return r.getClass().getSimpleName() + ":" + b.name;
// → "JsonTokenReader:..." for application/json
// → "CborTokenReader:..." for application/cbor
}
}

Argument resolvers​

The four arg resolvers are registered in DefaultConfig.restOpArgs:

Reader-side input is getReader() for ReaderParser, getInputStream() for binary parsers. Writer-side output is getNegotiatedWriter() / getNegotiatedOutputStream() similarly.

See also​