NullPointerException in HandlerManager when enabling Python addon on second instrument
Environment
- Bookmap version: 7.6.0 build 30
- Bookmap embedded Python: 3.9 (miniforge
bm39 env)
- OS: Windows 11 (WSL Ubuntu-24.04 used to host the addon source)
- Data provider: Rithmic Paper
- Instruments tested: NQM6.CME@RITHMIC, ESM6.CME@RITHMIC
- Strategy type: Simplified L1 Python addon
- Loader:
runpy.run_path(...) from Bookmap's Python editor
Summary
A Python addon that subscribes to multiple instruments via INSTRUMENT_ALIASES
crashes Bookmap with java.lang.NullPointerException in
HandlerManager.handle() immediately after the subscribe handler
fires for the second instrument. The crash happens regardless of which
instrument is enabled first (NQ then ES, or ES then NQ), and regardless
of the Python-side state. Single-instrument operation works perfectly.
Reproduction steps
- Create a Python addon (full code below) that calls
bm.create_addon,
adds trades + depth + on_interval handlers, and starts with
bm.start_addon(addon, subscribe_handler, unsubscribe_handler).
- Load the script in Bookmap via the Python editor:
import runpy
runpy.run_path(r'<path_to_script>', run_name='__main__')
- Enable the addon for the first instrument (e.g. NQM6.CME@RITHMIC).
Observe: subscribe handler fires correctly, addon starts processing.
snapshot.json is written every 100ms with valid data. ✅
- Enable the addon for the second instrument (e.g. ESM6.CME@RITHMIC),
without disabling the first. Observe:
- Python's
subscribe handler IS called for the second instrument
STATE dict reaches size 2
- ~2ms later, Bookmap throws
NullPointerException and unloads
the addon. ❌
The same crash happens in the reverse order (ES first, then NQ).
Stack trace
java.lang.NullPointerException: Cannot invoke
"com.bookmap.api.rpc.server.handlers.HandlerManager.handle(com.bookmap.api.rpc.server.data.utils.AbstractEvent)"
because "this.handlerManager" is null
at com.bookmap.api.rpc.server.EventLoop.lambda$new$0(EventLoop.java:27)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
After the NPE, Bookmap unloads the addon (Unloading strategy/indicator ... bm_producer.jar), the Python unsubscribe_instrument handler is
called for the first instrument, and the addon process exits. Settings
get persisted with disabling for ESM6.CME@RITHMIC flags so the
Strategy settings were saved before it was loaded last time message
appears on the next load.
Relevant Bookmap log excerpt
20260428 22:11:05.581 (AWT-EventQueue-1) Stratgies dialog: com.bookmap.api.rpc.server.addon.bm_producer true
20260428 22:11:05.581 (AWT-EventQueue-1) Enabled for NQM6.CME@RITHMIC true - started (Simplified L1)
20260428 22:11:05.613 (AWT-EventQueue-1) BrApi(...): Add-on "bm_producer" - Became available for broadcasts as a consumer.
20260428 22:11:05.614 [PYTHON-CLIENT] [bm_producer] subscribe alias=NQM6.CME@RITHMIC ... pips=0.25 size_mult=1.0
20260428 22:11:05.614 [PYTHON-CLIENT] [bm_producer] registered short=NQ trades+depth subscribed; STATE size=2
20260428 22:11:05.614 [RPC-SERVER] Received finish initialization message for NQM6.CME@RITHMIC
20260428 22:11:05.616 (AWT-EventQueue-1) Enabled for NQM6.CME@RITHMIC true - complete (Simplified L1)
20260428 22:11:05.618 ERROR: (Global Exception Handler) Intercepted exception from strategy
java.lang.NullPointerException: Cannot invoke "com.bookmap.api.rpc.server.handlers.HandlerManager.handle(...)" because "this.handlerManager" is null
at com.bookmap.api.rpc.server.EventLoop.lambda$new$0(EventLoop.java:27)
...
20260428 22:11:05.624 (AWT-EventQueue-1) Unloading strategy/indicator C:\Bookmap\Python\build\bm_producer.jar com.bookmap.api.rpc.server.addon.bm_producer
(In this excerpt, ES had been enabled first at 22:09:53 and worked
correctly for ~72 seconds. NQ was enabled at 22:11:05; the NPE
occurred at 22:11:05.618 — about 2ms after the Python-side subscribe
handler completed for NQ.)
Minimal repro script
"""Minimal reproduction of NPE when enabling addon on second instrument."""
import bookmap as bm
import threading
from datetime import datetime, timezone
def stamp():
return datetime.now(timezone.utc).isoformat(timespec="microseconds")
def log(msg):
print("[repro] [" + stamp() + "] " + msg, flush=True)
STATE = {}
def handle_subscribe_instrument(addon, alias, full_name, is_crypto, pips,
size_multiplier, instrument_multiplier,
supported_features):
log("subscribe alias=" + str(alias) + " (STATE size before=" + str(len(STATE)) + ")")
STATE[alias] = {"order_book": bm.create_order_book()}
bm.subscribe_to_trades(addon, alias, len(STATE))
bm.subscribe_to_depth(addon, alias, len(STATE) + 100)
log(" subscribed; STATE size=" + str(len(STATE)))
def handle_unsubscribe_instrument(addon, alias):
log("unsubscribe alias=" + str(alias))
STATE.pop(alias, None)
def handle_trades(addon, alias, price, size, is_otc, is_bid,
is_execution_start, is_execution_end,
aggressor_order_id, passive_order_id):
pass # no-op
def handle_depth(addon, alias, is_bid, price, size):
state = STATE.get(alias)
if state is not None:
bm.on_depth(state["order_book"], is_bid, price, size)
def on_interval(addon, alias):
pass # no-op
if __name__ == "__main__":
log("STARTUP, thread=" + threading.current_thread().name)
addon = bm.create_addon()
bm.add_trades_handler(addon, handle_trades)
bm.add_depth_handler(addon, handle_depth)
bm.add_on_interval_handler(addon, on_interval)
bm.start_addon(addon, handle_subscribe_instrument, handle_unsubscribe_instrument)
log("Addon started, blocking...")
bm.wait_until_addon_is_turned_off(addon)
log("Addon stopped.")
To reproduce:
- Save as
repro.py.
- Load in Bookmap via
runpy.run_path(r'<path>', run_name='__main__').
- Enable for any single instrument — works fine.
- Enable for a second instrument (without disabling the first) — NPE.
What I expected
Per the official start_addon documentation:
handle_subscribe_instrument is a function that you should define.
It will be called each time you enable the addon in Bookmap for a
certain instrument.
This wording suggests one Python addon should handle multiple
instruments by receiving multiple subscribe calls (one per
instrument). The official examples (mbo_test.py, cvd_addon.py,
liquidity_tracker.py, simple_market_maker.py) all use this pattern
with alias_to_* dicts, implying multi-instrument is the intended
design.
What actually happens
The subscribe handler fires correctly for the second instrument, but
Bookmap's internal EventLoop then tries to dispatch an event to a
HandlerManager that is null. This suggests the second-instrument
initialization path has a race condition or missing initialization on
the Java side.
Workaround currently in use
I've added a guard in my Python addon that refuses any subscribe call
beyond the first. This restricts the addon to one instrument per
session. The user must disable the addon entirely before switching to
a different instrument. This works but defeats the whole point of
multi-instrument addons.
def handle_subscribe_instrument(addon, alias, ...):
if len(STATE) >= 1:
log("REFUSED: already subscribed to " + str(list(STATE.keys())[0]))
return
# ... normal subscribe path ...
Questions for the team
- Is there a known issue with multi-instrument Simplified L1 Python
addons in Bookmap 7.6.0?
- Should the Python API support multi-instrument in the same addon,
or is it expected behavior that each instrument requires a separate
addon load?
- If multi-instrument is supported, is there an initialization order
or method I'm missing in the bootstrap?
- If NOT supported, what's the recommended pattern for an addon that
needs to read data from multiple instruments simultaneously (e.g.
to compute cross-instrument correlations)?
Happy to provide more logs, test other configurations, or run a debug
build if helpful.
Thanks!
NullPointerException in HandlerManager when enabling Python addon on second instrument
Environment
bm39env)runpy.run_path(...)from Bookmap's Python editorSummary
A Python addon that subscribes to multiple instruments via
INSTRUMENT_ALIASEScrashes Bookmap with
java.lang.NullPointerExceptioninHandlerManager.handle()immediately after thesubscribehandlerfires for the second instrument. The crash happens regardless of which
instrument is enabled first (NQ then ES, or ES then NQ), and regardless
of the Python-side state. Single-instrument operation works perfectly.
Reproduction steps
bm.create_addon,adds trades + depth + on_interval handlers, and starts with
bm.start_addon(addon, subscribe_handler, unsubscribe_handler).Observe: subscribe handler fires correctly, addon starts processing.
snapshot.jsonis written every 100ms with valid data. ✅without disabling the first. Observe:
subscribehandler IS called for the second instrumentSTATEdict reaches size 2NullPointerExceptionand unloadsthe addon. ❌
The same crash happens in the reverse order (ES first, then NQ).
Stack trace
After the NPE, Bookmap unloads the addon (
Unloading strategy/indicator ... bm_producer.jar), the Pythonunsubscribe_instrumenthandler iscalled for the first instrument, and the addon process exits. Settings
get persisted with
disabling for ESM6.CME@RITHMICflags so theStrategy settings were saved before it was loaded last timemessageappears on the next load.
Relevant Bookmap log excerpt
(In this excerpt, ES had been enabled first at 22:09:53 and worked
correctly for ~72 seconds. NQ was enabled at 22:11:05; the NPE
occurred at 22:11:05.618 — about 2ms after the Python-side
subscribehandler completed for NQ.)
Minimal repro script
To reproduce:
repro.py.runpy.run_path(r'<path>', run_name='__main__').What I expected
Per the official
start_addondocumentation:This wording suggests one Python addon should handle multiple
instruments by receiving multiple
subscribecalls (one perinstrument). The official examples (
mbo_test.py,cvd_addon.py,liquidity_tracker.py,simple_market_maker.py) all use this patternwith
alias_to_*dicts, implying multi-instrument is the intendeddesign.
What actually happens
The
subscribehandler fires correctly for the second instrument, butBookmap's internal
EventLoopthen tries to dispatch an event to aHandlerManagerthat isnull. This suggests the second-instrumentinitialization path has a race condition or missing initialization on
the Java side.
Workaround currently in use
I've added a guard in my Python addon that refuses any
subscribecallbeyond the first. This restricts the addon to one instrument per
session. The user must disable the addon entirely before switching to
a different instrument. This works but defeats the whole point of
multi-instrument addons.
Questions for the team
addons in Bookmap 7.6.0?
or is it expected behavior that each instrument requires a separate
addon load?
or method I'm missing in the bootstrap?
needs to read data from multiple instruments simultaneously (e.g.
to compute cross-instrument correlations)?
Happy to provide more logs, test other configurations, or run a debug
build if helpful.
Thanks!