Maximo Script Debugger
VS Code debug extension for Maximo automation scripts (Jython and Nashorn JS). The extension launches the Java Maximo-debugger DAP backend over stdio, and the backend attaches to a JDWP-enabled Maximo JVM.
Pre-release. This is an early pre-release that targets development/test Maximo environments. See Known Limitations and Security before using it.
Requirements
- VS Code 1.116 or newer.
- The MxScript extension (
Rana.mxscript) — required. It is a hard
dependency (VS Code installs/enables it automatically). It is the single
source of truth for your Maximo connection: the debugger reuses MxScript's
configured environment (host + authentication) to talk to Maximo and to
auto-deploy the in-Maximo debug helper. Configure at least one Maximo
environment in MxScript's Maximo Environments view, pointing at your debug
target.
- Java 17 or newer to run the debugger host process (the backend uses
jdk.jdi).
- Download a JDK from Eclipse Temurin: https://adoptium.net/temurin/releases/?version=17. Pick the JDK (not JRE) for your OS/arch, download the
.zip/.tar.gz, and extract it anywhere — no installer required. Then point the extension at it with mxscriptMaximoDebugger.javaHome (the extracted folder) or mxscriptMaximoDebugger.javaPath (the bin/java inside it). Run Maximo Debugger: Validate Java Runtime to confirm.
- A Maximo JVM started with JDWP enabled — see Enabling JDWP on Maximo.
- A runnable Maximo debugger backend jar at
server/maximo-debugger.jar (bundled), or an override path in debuggerJarPath / mxscriptMaximoDebugger.debuggerJarPath.
Maximo connection & the debug helper (auto-deployed)
The debugger gets which Maximo and how to authenticate entirely from the
MxScript environment — you do not put a host/URL/API key in launch.json.
The launch config adds only the JDWP transport:
{
"type": "mxscript-maximo-debugger",
"request": "attach",
"name": "Debug Maximo (DEV)",
"maximoEnvironment": "DEV", // optional: pin to a named MxScript environment; default = active
"jdwpPort": 7777, // JDWP debug port (default 7777)
"jdwpHost": "localhost" // optional: only when JDWP is tunnelled (oc port-forward/SSH); default = env hostname
}
For Jython debugging the in-Maximo helper (driver + line-fix agent) is a
hard prerequisite. On attach the extension silently deploys it to your
MxScript environment over that environment's authenticated REST session
(progress: "deploying debug helper…"). If the helper can't be deployed, the
debugger refuses to connect (you'll get a clear error). The helper is a hot,
non-persistent deploy — it is re-deployed automatically on the next attach after
a Maximo restart. Nashorn-only sessions don't need the helper and skip this.
The backend jar is bundled with the published extension — there is nothing
to build. (Contributors building from source: see
docs/building-from-source.md.)
Sample Attach Configuration
Minimal — the Maximo host + authentication come from the MxScript environment, so you don't put host, a URL, or credentials here:
{
"type": "mxscript-maximo-debugger",
"request": "attach",
"name": "Debug Maximo (DEV)",
"maximoEnvironment": "DEV",
"jdwpPort": 7777,
"jythonSourceRoots": ["${workspaceFolder}/scripts/jython"],
"nashornSourceRoots": ["${workspaceFolder}/scripts/nashorn"]
}
maximoEnvironment — optional; the MxScript environment to use (defaults to the active one).
jdwpPort — the JDWP debug port (default 7777).
jdwpHost — optional; only when JDWP is tunnelled to a different host than the environment (e.g. localhost via oc port-forward/SSH). Defaults to the environment hostname.
- Jython and Nashorn are debugged together — no engine modes. A single JDWP
attach drives both engines: a
.py breakpoint routes to the Jython engine and a
.js breakpoint routes to Nashorn. Set jythonSourceRoots and/or
nashornSourceRoots for whichever languages you debug (set both to debug both).
(Only one JDWP session per JVM is supported, so this one session covers everything.)
diveIntoJdk — optional (default true); when stepping into Java, also descend into useful JDK packages (java.util, java.time, java.math, java.text, java.io). Set false to restrict step-into to Maximo app code (psdi.* / com.ibm.tivoli.maximo.*). See Stepping into Java.
host / port are deprecated (still honored for back-compat → treated as jdwpHost / jdwpPort). Prefer maximoEnvironment + jdwpPort.
Everything else is optional and has sensible defaults — sourceMappingMode (hybrid), nashornVariableSummaryMode (safeLazy), attachTimeoutMillis, resumeOnDisconnect, etc. Override only when needed.
Enabling JDWP on Maximo (MAS 9 & Maximo 7.6)
The extension attaches to the Maximo JVM over JDWP (the standard Java debug wire protocol). You enable it by adding one JVM argument and exposing the port. This brief follows the approach in Sharptree's "remote debugging Maximo automation scripts" guidance (https://sharptree.io); adapt it to your environment.
The JVM argument is the same on both versions:
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:7777
server=y — the JVM listens; the debugger connects in.
suspend=n — the JVM does not wait for a debugger at startup.
address=*:7777 — listen on port 7777 on all interfaces (use a specific bind address if your platform requires it).
Maximo 7.6 (traditional WebSphere):
- WebSphere admin console → Servers → Server Types → WebSphere application servers →
<your MXServer> → Process definition → Java Virtual Machine.
- Add the
-agentlib:jdwp=... line above to Generic JVM arguments, save, and synchronize the node.
- Restart the MXServer JVM.
- Make sure port
7777 is reachable from your workstation (host firewall / network).
MAS 9 (containerized — OpenLiberty on OpenJ9 in OpenShift/Kubernetes):
- Add the
-agentlib:jdwp=... argument to the server's JVM options (e.g. a jvm.options entry or the JVM_ARGS/server-customization mechanism your MAS deployment uses).
- Roll/restart the Manage (
*-manage / *-wlp-*) pod so the JVM picks up the argument.
- Expose port
7777 from the pod — e.g. a Service/NodePort, an OpenShift Route (TCP), or oc port-forward svc/<manage-svc> 7777:7777 for a quick local session.
The bundled in-Maximo helper also exposes a runtime enableJdwp action that can load the JDWP agent into an already-running JVM without a restart, but loading the debug agent at runtime is environment-dependent and is not a substitute for the supported boot-argument approach above.
The in-Maximo helper (auto-deployed)
Accurate Jython line + exception breakpoints need an in-Maximo helper — a DebugJSR223ScriptDriver plus a PyxLineAgent that rewrites Jython's broken _pyx line tables so JDI breakpoints land on the right line. The extension bundles these jars and deploys them for you:
- On a Jython attach the extension uploads + activates the helper automatically (hot, no Maximo restart) over the MxScript environment's authenticated REST session, showing a "deploying debug helper…" progress notification. It's a hard prerequisite — if the deploy fails, the debugger refuses to connect with a clear error.
- It's idempotent and self-updating: a version handshake re-uploads only when the deployed copy is older than the bundled one, and it's re-deployed automatically after a Maximo restart (the helper is a hot, non-persistent install) on the next attach.
- Nashorn-only sessions don't need the helper and skip the deploy.
You don't run any manual command for this.
Jython debugging is agent-only and tuned for low latency. Breakpoints and
stepping use real per-_pyx JDI line breakpoints — there is no slow
PyFrame.setline per-line hook — so stepping doesn't flood JDWP. The Variables
pane is served lazily, and on each stop the helper agent captures the frame's
locals/globals in a single in-process call rather than one round-trip per
variable, so it stays responsive even against remote/LAN Maximo targets. The
helper self-updates on attach to enable this; if it can't be deployed, Jython
breakpoints won't bind and the extension prompts you to (re)deploy it.
Stepping stays fast on large scripts — the compiled line table used to arm each
step is cached per class — and fetching variables while you step quickly no longer
stalls: an in-flight fetch for a stop you've already left is abandoned rather than
blocking.
Nashorn Variables
Nashorn variables are displayed in VS Code's normal Variables tree. safeLazy is the default mode: locals show cheap scalar summaries immediately, while objects, arrays, Java collections, maps, custom classes, globals, closures, and implicit runtime metadata are expanded only when you open that node in the tree.
The backend owns Nashorn value inspection and emits DAP type, variablesReference, and child count metadata so the extension does not need a custom reveal button or separate variable UI.
Stepping into Java (decompiled)
From a Jython script you can Step Into a line that calls Java and the
debugger descends into the actual Java method — psdi.*, com.ibm.tivoli.maximo.*,
and (by default) useful JDK classes like java.util.Date — instead of stepping
over it. For example, on asset.getString("assetnum") you land in
psdi.mbo.Mbo.getString; on d.setTime(...) you land in java.util.Date.setTime.
- One continuous call stack. Java frames appear above your script frames; the
Jython dispatch/reflection plumbing in between is collapsed, so it reads as one
script → Java stack. Step Out of the Java frames returns to the next line of
your script; stepping within Java uses native JDI stepping.
- Decompiled source, correct line. Maximo/JDK classes ship no
.java, so Java
frames are decompiled on demand (Vineflower) from the exact loaded bytecode
fetched via the in-Maximo helper agent, and shown at the right line via the
bytecode→source mapping. Results are cached per class.
- Java variables. Locals (where the class carries debug info) and
this
(expandable to its fields) show in the Variables pane. Static, no-argument
methods legitimately have no locals and no this.
- JDK scope toggle.
diveIntoJdk (default true) includes the useful JDK
packages above; set it false for Maximo-app-only stepping. Jython's own
dispatch internals (java.util.concurrent, reflection, OpenJ9/Liberty
internals) are always skipped so steps land on your real target.
Requires the in-Maximo helper (auto-deployed for Jython sessions) for the
decompiler's bytecode fetch. Nashorn → Java diving is not yet supported,
and only the top Java frame is decompiled (deeper Java caller frames show
without source for now).
Commands
Maximo Debugger: Open Logs
Maximo Debugger: Copy Sample Launch Config
Maximo Debugger: Copy Launch Diagnostics
Maximo Debugger: Validate Java Runtime
Maximo Debugger: Show Recent Script Executions
Maximo Debugger: Deploy Helper Driver + Agent
Maximo Debugger: Pick Debug Script (Catalog)
Settings
mxscriptMaximoDebugger.javaPath: Java executable used to start the backend. Defaults to java.
mxscriptMaximoDebugger.debuggerJarPath: Optional backend jar override.
mxscriptMaximoDebugger.debuggerLibPath: Optional backend dependency jar directory.
mxscriptMaximoDebugger.logLevel: Backend log level.
mxscriptMaximoDebugger.traceAdapter: Show resolved backend command and verbose diagnostics.
The attach configuration also supports per-session overrides for javaPath, javaArgs, debuggerJarPath, debuggerLibPath, and logLevel.
Use Maximo Debugger: Copy Launch Diagnostics to copy the resolved workspace roots, backend paths, and computed Java command for the current default attach configuration.
Known Limitations
This is a pre-release. Be aware of the following before relying on it:
- Re-attach may hang the JVM's JDWP listener. On some JVMs (notably OpenJ9
with
server=y libjdwp), the JDWP server-side socket can wedge after a
couple of attach/detach cycles — a fresh attach then stalls or the port
becomes unreachable. The extension does its best to fully resume threads and
tear down its JDI event requests on disconnect, but if attaches start
failing, restart the Maximo JVM (the pod/server) to clear the stuck
listener. This is a JVM/JDWP behavior, not something the extension can fully
work around.
- Conditional breakpoints, hit-count breakpoints, and logpoints are
experimental on Jython. They are evaluated against the in-Maximo compiled
_pyx frame and can be intermittent — a condition that should pause may
occasionally not, or vice versa — because the expression evaluation shares
the same suspended-thread dispatch as the stop. Treat them as best-effort.
Plain (unconditional) line breakpoints are reliable. Nashorn conditions are
more stable than Jython.
- Exception breakpoints are Jython-only and don't distinguish caught vs.
uncaught. Toggling "Uncaught Exceptions" / "All Exceptions" pauses on
exceptions raised in your script (highlights the line, shows the stack +
variables, and the type/message). Because Jython wraps every Python exception
in a Java
PyException that its interpreter catches internally, the JDI
caught/uncaught distinction doesn't map to Python — so both toggles behave the
same: stop on script exceptions. Scripts that use try/except for control
flow will pause on those too while enabled; untick to disable.
- Helper redeploy after restart is automatic. The in-Maximo helper
driver/agent is a hot, non-persistent deploy; it is re-deployed automatically
via MxScript on the next attach after a Maximo restart.
- One JDWP debug session at a time per Maximo JVM is the supported mode.
Security
JDWP is a remote-code-execution channel. Enable it only on development and
test environments you control.
- An open JDWP port lets anyone who can reach it run arbitrary code inside
the Maximo JVM with full application privileges — there is no authentication
on the debug protocol.
- Never enable JDWP on a production Maximo, and never expose the JDWP port
to the public internet or an untrusted network. Bind it to localhost or a
private network and use a VPN / SSH tunnel /
oc port-forward to reach it.
- Remove the
-agentlib:jdwp=... JVM argument (and any exposed
port/route/service) when you are done debugging.
- The in-Maximo helper driver + agent execute with Maximo's privileges and can
rewrite loaded classes — deploy them only to environments you trust and
intend to debug.
Troubleshooting
- If attach fails before the backend starts, run
Maximo Debugger: Validate Java Runtime.
- If a Jython attach is blocked with a deploy/connection error, confirm MxScript has an environment selected for your target and that you allowed this extension access to it.
- If breakpoints stay unverified, check
jythonSourceRoots, nashornSourceRoots, and sourceMappingMode.
- If the target does not accept JDWP attach, confirm the Maximo JVM is listening on the configured host and port.