In native-bridge.js, toNative() gives each call a string id from a counter seeded with a random number, stores the call's resolve and reject (or, for addListener, its callback) in a Map under that id, and posts { callbackId, pluginId, methodName, options }. The probe passes getBook() a Date and an undefined field; the verbose log shows what Java received:
adb -s emulator-5558 logcat -d -v tag -s Capacitor Capacitor/Console \
| grep -E "methodData|Sending|Msg: (resolved|rejected|event)" | sed 's/File: .* Msg: //'V/Capacitor: callback: 41746391, pluginId: Shelf, methodName: addListener, methodData:
{"eventName":"stockChanged"}
V/Capacitor: callback: 41746392, pluginId: Shelf, methodName: getBook, methodData:
{"id":3,"at":"2026-09-23T00:00:00.000Z"}
I/Capacitor/Console: resolved {"id":3,"title":"Salt and
Saffron","price":24,"seenAt":"2026-09-23T00:00:00.000Z"}
V/Capacitor: callback: 41746393, pluginId: Shelf, methodName: getBook, methodData: {"id":99}
D/Capacitor: Sending plugin error: {"save":false,"callbackId":"41746393","pluginId":"Shelf","me
thodName":"getBook","success":false,"error":{"message":"No book with id
99","code":"NOT_FOUND"}}
I/Capacitor/Console: rejected code=NOT_FOUND message=No book with id 99
V/Capacitor: callback: 41746394, pluginId: Shelf, methodName: restock, methodData: {"id":3}
I/Capacitor/Console: event {"id":3,"inStock":true}JSON.stringify did the serializing. The Date arrived as an ISO string, from its toJSON(); note vanished; and 24.00 came back as 24, since JSON has one number type. By the same rules NaN becomes null, a Map or a Uint8Array becomes a plain object, and a BigInt throws, so send binary data as a base64 string, as the Filesystem plugin does. returnResult() copies a reply's error fields onto an Error, hence err.code, and deletes a promise's entry after its one result. addListener is kept alive natively instead, so id 41746391 stays in the map, and each notifyListeners() answers that stored call again: the stockChanged event, 500 ms after restock() resolved.