JSON-RPC Reference
This page is the complete reference for PAL's JSON-RPC 2.0 API: wire-format messages, supported methods, argument types, error codes, validation rules, and the JsonRpcMessageFactory Java helper for building requests programmatically.
For an overview of how RPC fits into PAL, see Remote Procedure Calls. For the binary RPC counterpart (Colfer over ZeroMQ), see Binary RPC (MessageBuilder). For multi-step workflows with automatic ObjectRef tracking, see the RpcChain DSL.
Message Structure
Every request follows the JSON-RPC 2.0 envelope:
Every response is either a result or an error (never both):
Result envelope
A non-error result always carries either void: true or void: false plus a value object:
void: true— the method returnedvoid; novalueis present.void: false— the method returned a value, described byvalue:value.type— the Java type of the return value (always present).value.null: true— the return value isnull(novalue.valueorvalue.reffollows).value.ref: N— the return value is a non-primitive object;Nis its integer ObjectRef ID, usable in subsequent calls.value.value: "..."— the return value is a primitive, wrapper, or string. Scalars are rendered as JSON strings (e.g."value": "8"for anintresult of8), since JSON does not natively distinguishint,long,double, etc.; use thetypefield to coerce the literal on the client side.
Concrete examples for each shape appear in the Methods section below.
Methods
PAL recognises six top-level methods: new, call, get, put, control, and meta.
new — Constructor
Creates a new object on the peer. Returns an ObjectRef that can be used in subsequent calls.
With constructor arguments:
{
"jsonrpc": "2.0", "id": "2",
"method": "new",
"params": {
"type": "com.example.User",
"args": [
{"type": "java.lang.String", "value": "john"},
{"type": "int", "value": 30}
]
}
}
Response — the ref field is the ObjectRef ID:
{
"jsonrpc": "2.0", "id": "2",
"result": {
"void": false,
"value": {
"type": "com.example.User",
"null": false,
"ref": 42
}
}
}
call — Method Invocation
Static method
Omit instance to call a static method:
{
"jsonrpc": "2.0", "id": "3",
"method": "call",
"params": {
"type": "com.example.Calculator",
"method": "add",
"args": [
{"type": "int", "value": 5},
{"type": "int", "value": 3}
]
}
}
Response — value result:
{
"jsonrpc": "2.0", "id": "3",
"result": {
"void": false,
"value": {
"type": "int",
"null": false,
"value": "8"
}
}
}
Instance method
Include instance with the ObjectRef ID from a prior new or call:
{
"jsonrpc": "2.0", "id": "4",
"method": "call",
"params": {
"type": "java.util.ArrayList",
"method": "add",
"instance": 42,
"args": [
{"type": "java.lang.String", "value": "hello"}
]
}
}
Void method
When the method returns void, the response has "void": true:
Returning an ObjectRef
When a method returns a non-primitive object, the response includes a ref:
{
"jsonrpc": "2.0", "id": "5",
"result": {
"void": false,
"value": {
"type": "com.example.Calculator",
"null": false,
"ref": 99
}
}
}
Returning null
{
"jsonrpc": "2.0", "id": "6",
"result": {
"void": false,
"value": {
"type": "java.lang.String",
"null": true
}
}
}
Note the asymmetry between passing null as an argument and receiving null as a return: arguments use {"type": "...", "value": null} (the JSON literal null under the value key), while responses use {"type": "...", "null": true} (a boolean key).
get — Field Read
Static field
{
"jsonrpc": "2.0", "id": "7",
"method": "get",
"params": {
"type": "com.example.Config",
"field": "VERSION"
}
}
Instance field
Include instance to read from a specific object:
{
"jsonrpc": "2.0", "id": "8",
"method": "get",
"params": {
"type": "com.example.User",
"field": "name",
"instance": 42
}
}
put — Field Write
Static field
The value field is a typed argument (same format as args entries):
{
"jsonrpc": "2.0", "id": "9",
"method": "put",
"params": {
"type": "com.example.Config",
"field": "debugMode",
"value": {"type": "boolean", "value": true}
}
}
Instance field
{
"jsonrpc": "2.0", "id": "10",
"method": "put",
"params": {
"type": "com.example.User",
"field": "name",
"instance": 42,
"value": {"type": "java.lang.String", "value": "jane"}
}
}
Setting a field to null:
{
"jsonrpc": "2.0", "id": "11",
"method": "put",
"params": {
"type": "com.example.User",
"field": "nickname",
"instance": 42,
"value": {"type": "java.lang.String", "value": null}
}
}
put always returns a void result.
control — Session Management
Control messages manage object lifecycle on the peer and provide basic peer-level commands. They use method: "control" at the top level, with a sub-method in params.
Delete an object
Removes a single object from the peer's session. Subsequent operations on this ObjectRef will fail with NullPointerException.
{
"jsonrpc": "2.0", "id": "12",
"method": "control",
"params": {
"method": "delete_object",
"args": [{"ref": 42}]
}
}
Delete a session
Removes all objects from the caller's session on the peer.
Trigger garbage collection
Requests the peer to run System.gc().
Ping
Liveness check. Accepts no arguments and returns void. Useful for verifying that a peer's RPC interface is reachable without performing any side effects.
JsonRpcMessageFactory does not currently expose a builder for ping; build it from raw JSON if you need it from Java.
All control messages return a void result.
meta — Metadata Query
Queries metadata about RPC-accessible classes, methods, and fields on the peer. The meta method is a placeholder for future metadata services; currently it supports a single service: fetch_classes_info.
Fetch classes info
Scans the peer's classpath and returns metadata for the classes, constructors, methods, and fields the caller is permitted to invoke via RPC. The output is filtered through the peer's currently active RPC policy, so what you see is exactly what you can call — anything the policy denies is omitted. Policy hot-reloads are visible on the next scan, so re-issuing the request after a policy change reflects the new permissions without restarting the peer.
Use this to discover the callable surface of a remote peer without prior knowledge of its codebase, or to verify what a given policy actually exposes.
{
"jsonrpc": "2.0", "id": "16",
"method": "meta",
"params": {
"method": "fetch_classes_info",
"args": [
{"name": "compress_encode", "type": "boolean", "value": true},
{"name": "merge_ancestry", "type": "boolean", "value": false},
{"name": "exclude_prefixes", "type": "java.lang.String[]", "value": ["java.lang.", "sun.misc."]},
{"name": "include_classes", "type": "java.lang.String[]", "value": ["com.example.MyClass"]}
]
}
}
| Argument | Type | Default | Description |
|---|---|---|---|
compress_encode |
boolean | true |
Compress and base64-encode the response body. Class metadata for a real classpath can be very large, so compression is on by default; set to false only when you want raw JSON for inspection. |
merge_ancestry |
boolean | false |
Include inherited methods and fields from superclasses and interfaces. |
exclude_prefixes |
string[] | (none) | Class-name prefixes to exclude (e.g., java.lang., sun.misc.). |
include_classes |
string[] | (all) | When set, only the listed fully-qualified class names are included. |
The response carries a status code (OK, ERROR, UNAUTHORIZED, UNSUPPORTED) and a body containing either the encoded class metadata or an error message.
In Java, prefer JsonRpcMessageFactory.buildFetchClassesInfoMetaMessage over hand-built JSON for meta requests.
Argument Types
Arguments appear in the args array and in the value field of put requests. Each argument is an object with type and value, or an object reference with ref.
Primitives and wrappers
{"type": "int", "value": 42}
{"type": "long", "value": 9223372036854775807}
{"type": "double", "value": 3.14}
{"type": "float", "value": 1.5}
{"type": "boolean", "value": true}
{"type": "byte", "value": 127}
{"type": "short", "value": 32767}
{"type": "char", "value": "A"}
Wrapper types use the fully-qualified name:
Strings
Null values
Specify the type but set the value to null:
Object references
Pass a previously obtained ObjectRef by its ID:
This allows chaining: create an object with new, then pass it as an argument to a call.
Arrays
Arrays use type to specify the array type and value as a JSON array:
{"type": "int[]", "value": [1, 2, 3]}
{"type": "double[]", "value": [1.5, 2.5, 3.5]}
{"type": "String[]", "value": ["a", "b", "c"]}
Null and empty arrays:
Numeric suffixes
The outer type (e.g. "double[]") tells PAL the array's component type, but JSON has no native distinction between int, long, double, and float — every JSON number arrives as the same generic numeric token. Append a suffix to each inner literal to disambiguate:
| Suffix | Type |
|---|---|
d |
double (3.14d) |
f |
float (1.5f) |
l |
long (100l) |
Integer values need no suffix. Example:
{"type": "double[]", "value": [239823d, 38723d, 2323d]}
{"type": "float[]", "value": [23f, 1f, 3f]}
{"type": "long[]", "value": [2398239l, -23l]}
Collections
ArrayList
{
"type": "java.util.ArrayList",
"value": [
{"type": "java.lang.Integer", "value": 39},
{"type": "java.lang.Integer", "value": 5},
{"type": "java.lang.Integer", "value": 58}
]
}
HashMap
Params Reference
Summary of all params fields:
| Field | Type | Used by | Description |
|---|---|---|---|
type |
string | new, call, get, put |
Fully-qualified class name |
method |
string | call, control, meta |
Method name (or sub-method name for control / meta) |
field |
string | get, put |
Field name to read or write |
instance |
integer | call, get, put |
ObjectRef ID for instance operations (omit for static) |
args |
array | new, call, control, meta |
Typed argument array |
value |
object | put |
Typed argument for the new field value |
Required fields per method:
| Method | Required | Optional |
|---|---|---|
new |
type |
args |
call |
type, method |
instance, args |
get |
type, field |
instance |
put |
type, field, value |
instance |
control |
method |
args |
meta |
method |
args |
Error Response Format
When an operation fails, the response contains an error object with a standard JSON-RPC 2.0 error code, a human-readable message, and a data object with details:
{
"jsonrpc": "2.0", "id": "1",
"error": {
"code": -32601,
"message": "Method not found",
"data": {
"throwable_type": "java.lang.ClassNotFoundException",
"message": "com.example.DoesNotExist",
"request_id": "1",
"stack_trace": ["..."],
"cause": null
}
}
}
Error codes
| Code | Name | When |
|---|---|---|
| -32700 | Parse error | Malformed JSON (syntax error) |
| -32600 | Invalid Request | Missing or unrecognised method field |
| -32602 | Invalid params | Missing required params (type, field, value), invalid characters in type name, Java reserved keyword used as type |
| -32601 | Method not found | ClassNotFoundException, NoSuchMethodException, NoSuchFieldException |
| -32603 | Internal error | Unexpected server-side failure |
| -32000 | Server error | Generic server error |
| -32001 | RPC access denied | Operation blocked by RPC policy |
Common error scenarios
Class not found:
{"code": -32601, "message": "Method not found",
"data": {"throwable_type": "java.lang.ClassNotFoundException",
"message": "com.example.DoesNotExist"}}
No matching method/constructor:
{"code": -32601, "message": "Method not found",
"data": {"throwable_type": "java.lang.NoSuchMethodException",
"message": "No matching constructor found"}}
Runtime exception in remote method:
{"code": -32000, "message": "Server error",
"data": {"throwable_type": "java.lang.ArithmeticException",
"message": "/ by zero",
"stack_trace": ["..."]}}
Missing required field:
{"code": -32602, "message": "Invalid params",
"data": {"message": "Field is missing in 'get' request"}}
Input Validation
The peer validates requests before dispatching. The following are rejected with -32602 Invalid params:
- Missing
paramsobject entirely - Missing
typeinparams typecontaining invalid characters (e.g.,:,/)typethat is a Java reserved keyword (e.g.,try,class)callwithoutmethodin paramsgetwithoutfieldin paramsputwithoutfieldorvaluein params
Invalid or unrecognised top-level method values (anything other than new, call, get, put, control, meta) are rejected with -32600 Invalid Request.
Member Visibility
PAL's JSON-RPC dispatch can access members of any Java visibility level — public, protected, package-private, and private. This is powerful for testing and debugging, but should be restricted in production.
Use the RPC policy system to control which visibility levels are accessible. The deny-nonpublic preset restricts RPC to public members only:
pal run -d localhost:2379 --json-rpc auto \
--rpc-policy-preset deny-nonpublic,deny-unsafe \
-cp app.jar com.example.Main
See RPC Policy — Visibility Filtering for fine-grained control.
Typical Session
A complete interaction often follows this pattern:
Client Peer
│ │
│── new Calculator ────────────────▶│
│◀──────────────── ref:42 ──────────│
│ │
│── call add(5,3) on ref:42 ───────▶│
│◀──────────────── value:8 ─────────│
│ │
│── get "result" on ref:42 ────────▶│
│◀──────────────── value:8 ─────────│
│ │
│── control delete_object ref:42 ──▶│
│◀──────────────── void ────────────│
For multi-step workflows in Java code, the RpcChain DSL handles ObjectRef management automatically.
JsonRpcMessageFactory
Module: pal-api — io.quasient.pal.serdes.jsonrpc.JsonRpcMessageFactory
The factory is a static utility class that builds JsonRpcRequest objects programmatically. It sits between raw JSON and the RpcChain DSL: you get type-safe request construction and automatic ID generation, but you still send each request individually and manage ObjectRefs yourself.
Building arguments
Arguments are constructed with Argument.builder():
import io.quasient.pal.messages.jsonrpc.Argument;
// Typed value
Argument intArg = Argument.builder().withValue(42).withType("int").build();
Argument strArg = Argument.builder().withValue("hello").withType("java.lang.String").build();
// Null value (type required)
Argument nullArg = Argument.builder().withValue(null).withType("java.lang.Integer").build();
// Object reference (from a prior response)
Argument refArg = Argument.builder().withRef(objectRef).build();
// Collection
ArrayList<Integer> list = new ArrayList<>(List.of(1, 2, 3));
Argument listArg = Argument.builder().withValue(list).withType("java.util.ArrayList").build();
Constructor call
import io.quasient.pal.serdes.jsonrpc.JsonRpcMessageFactory;
// No-arg constructor
JsonRpcRequest req = JsonRpcMessageFactory.buildConstructorCall(
"com.example.Calculator", List.of());
// With arguments
JsonRpcRequest req = JsonRpcMessageFactory.buildConstructorCall(
"com.example.User",
List.of(
Argument.builder().withValue("Alice").withType("java.lang.String").build(),
Argument.builder().withValue(30).withType("int").build()
));
Static method call
JsonRpcRequest req = JsonRpcMessageFactory.buildClassMethodCall(
"com.example.Calculator", "add",
List.of(
Argument.builder().withValue(5).withType("int").build(),
Argument.builder().withValue(3).withType("int").build()
));
Instance method call
Pass an ObjectRef (from a constructor or method response) or a raw integer ID:
// With ObjectRef
JsonRpcRequest req = JsonRpcMessageFactory.buildInstanceMethodCall(
"java.util.ArrayList", "add", objectRef,
List.of(Argument.builder().withValue("item").withType("java.lang.String").build()));
// With integer ID
JsonRpcRequest req = JsonRpcMessageFactory.buildInstanceMethodCall(
"java.util.ArrayList", "size", 42, List.of());
Field access
// Static get / put
JsonRpcRequest get = JsonRpcMessageFactory.buildStaticFieldGet(
"com.example.Config", "VERSION");
JsonRpcRequest put = JsonRpcMessageFactory.buildStaticFieldPut(
"com.example.Config", "debugMode",
Argument.builder().withValue(true).withType("boolean").build());
// Instance get / put
JsonRpcRequest get = JsonRpcMessageFactory.buildInstanceFieldGet(
"com.example.User", objectRef, "name");
JsonRpcRequest put = JsonRpcMessageFactory.buildInstanceFieldPut(
"com.example.User", objectRef, "name",
Argument.builder().withValue("Bob").withType("java.lang.String").build());
Meta and control messages
// Fetch classes info (metadata query)
JsonRpcRequest req = JsonRpcMessageFactory.buildFetchClassesInfoMetaMessage(
new String[]{"com.example.MyClass"}, // includeClasses (or null)
new String[]{"java.lang.", "sun.misc."}, // excludePrefixes (or null)
true, // compressAndEncode
false); // mergeAncestry
// Delete a single object from the session
JsonRpcRequest req = JsonRpcMessageFactory.buildDeleteObjectCommandMessage(objectRef);
// Delete the entire session
JsonRpcRequest req = JsonRpcMessageFactory.buildDeleteSessionCommandMessage();
// Trigger garbage collection
JsonRpcRequest req = JsonRpcMessageFactory.buildGcCommandMessage();
The factory does not currently expose a builder for the ping control sub-method; build it from raw JSON if needed.
Sending requests
The factory builds requests; sending them is done via ThinPeer. For ThinPeer setup (lookup by UUID/name, direct address, configuring with logs), see Binary RPC → Connecting to a Peer — the same setup applies, except withOutboundRpcType(RpcType.JSON_RPC) is used in place of RpcType.ZMQ_RPC.
// Send to peer and wait for response
JsonRpcResponse response = thinPeer.sendJsonRpcRequestToPeer(request).get();
// Check result
if (response.getError() != null) {
// Handle error
System.err.println(response.getError().getData().getMessage());
} else if (response.getResult().getIsVoid()) {
// Void return
} else {
// Extract value or ref
ResponseObject value = response.getResult().getValue();
Integer ref = value.getRef(); // ObjectRef ID (if object)
String type = value.getType(); // Return type
}
When to use the factory vs RpcChain
Use the factory when you need to:
- Send a single request (no chaining needed)
- Control request timing or ordering yourself
- Interleave RPC calls with other logic between each step
- Work at a lower level than the DSL allows
Use the RpcChain DSL when you have multi-step workflows with object dependencies — it eliminates the ObjectRef tracking boilerplate that the factory requires you to do manually.
Further Reading
- Remote Procedure Calls — Concept overview and RPC system architecture
- Binary RPC (MessageBuilder) — Binary RPC API for high-performance Java-to-Java communication
- RpcChain DSL — Java DSL for multi-step JSON-RPC workflows with automatic ObjectRef tracking
- RPC Policy — Access control for RPC operations
- CLI Reference — Complete
pal peer callandpal log calldocumentation