> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nelieo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# JVM Session Helpers for Java Application Automation

> Use JVMSessionProxy (session.jvm) to read Java fields, invoke methods, enumerate loaded classes, and map JVM types to Python for any tracked Java app.

`session.jvm` is a `JVMSessionProxy` — available on every `NSPSession` attached to a Java application. It provides direct, runtime-level access to the JVM through JVMTI and JNI: read static and instance fields, invoke methods, and enumerate loaded classes — all without modifying the target application or adding any Java code.

## When to Use `session.jvm`

The high-level state on `session.state` reflects the semantic model that the NSP JVM probe has already extracted. `session.jvm` lets you reach below that model to read raw Java fields and call arbitrary methods that are not yet surfaced in the semantic layer.

* **Start with `session.get_int(...)`, `session.get_str(...)`, etc.** for values already in the semantic state.
* **Use `session.jvm`** when you need internal Java fields, collection contents, or method return values not present in the semantic model.

<Note>
  JVM probe attachment via JVMTI takes longer on first connect than CLR or v8 probes — typically 2–15 seconds. If you call `session.jvm` methods immediately after `client.attach()`, you may receive `NSPActionError` with code `jvm_not_attached`. Wait for `session.confidence >= 0.85` or use `client.attach("AppName", fresh=True)` to ensure the probe is ready before issuing JVM calls.
</Note>

## Prerequisites

```python theme={null}
from nelieo_nsp import RuntimeKind

# Guard before using JVM helpers
if session.state.runtime == RuntimeKind.JVM and session.confidence >= 0.85:
    alt = await session.jvm.read_field("com.ardupilot.GCS", "altitude")
else:
    print(f"JVM probe not ready — conf={session.confidence:.2f}, waiting...")
    await session.wait_for(
        lambda s: s.confidence >= 0.85,
        timeout=30.0,
        poll_interval=1.0,
    )
```

## `read_field(class_name, field_name, *, timeout_ms)`

Read the value of a static or instance field from a loaded Java class.

<ParamField path="class_name" type="str" required>
  The fully-qualified Java class name, e.g. `"com.ardupilot.GCS"`. Use dots as separators (not slashes).
</ParamField>

<ParamField path="field_name" type="str" required>
  Field name, case-sensitive, exactly as declared in the Java source.
</ParamField>

<ParamField path="timeout_ms" type="int" default="10000">
  Maximum time in milliseconds to wait for the JVMTI agent to respond.
</ParamField>

```python theme={null}
alt    = await session.jvm.read_field("com.ardupilot.GCS", "altitude")
mode   = await session.jvm.read_field("com.ardupilot.GCS", "flightMode")
armed  = await session.jvm.read_field("com.ardupilot.GCS", "isArmed")

print(f"Alt: {alt:.1f}m  Mode: {mode}  Armed: {armed}")
```

Python types returned by `read_field()`:

| Java Type                      | Python Type |
| ------------------------------ | ----------- |
| `boolean` / `Boolean`          | `bool`      |
| `int`, `long`, `short`, `byte` | `int`       |
| `double`, `float`              | `float`     |
| `String`                       | `str`       |
| `null`                         | `None`      |
| `List<T>`, `T[]`               | `list`      |
| `Map<K, V>`                    | `dict`      |

## `invoke(class_name, method_name, args, *, timeout_ms)`

Invoke a Java method by name and return its result. Arguments are automatically coerced to the JVM types expected by the method's signature.

<ParamField path="class_name" type="str" required>
  Fully-qualified Java class name.
</ParamField>

<ParamField path="method_name" type="str" required>
  Method name, case-sensitive.
</ParamField>

<ParamField path="args" type="list[Any]" default="None">
  Positional arguments. Each value is coerced to the JVM type expected by the method signature.
</ParamField>

<ParamField path="timeout_ms" type="int" default="10000">
  Maximum time to wait for the method to return.
</ParamField>

```python theme={null}
# Parameterless method
status = await session.jvm.invoke("com.ardupilot.GCS", "getStatusText")
print(status)   # "Landed, Disarmed"

# Single string argument
await session.jvm.invoke("com.ardupilot.GCS", "setMode", ["GUIDED"])

# Multiple numeric arguments — coerced to Java double, double, double
await session.jvm.invoke(
    "com.ardupilot.WaypointManager",
    "addWaypoint",
    [34.052, -118.243, 150.0],
)

# Boolean argument
await session.jvm.invoke("com.ardupilot.GCS", "setArmed", [True])
```

### Type Coercion Reference

| Python Value     | JVM Target                     |
| ---------------- | ------------------------------ |
| `True` / `False` | `boolean` / `Boolean`          |
| `int`            | `int`, `long`, `short`, `byte` |
| `float`          | `double`, `float`              |
| `str`            | `String`                       |
| `list`           | `List<T>`, `T[]`               |
| `dict`           | `Map<String, Object>`          |
| `None`           | `null`                         |

## `list_classes(*, prefix, timeout_ms)`

Enumerate all classes currently loaded in the JVM, optionally filtered to a package prefix.

<ParamField path="prefix" type="str" default="&#x22;&#x22;">
  Return only classes whose fully-qualified name starts with this prefix.
</ParamField>

<ParamField path="timeout_ms" type="int" default="10000">
  Maximum time to wait for the class enumeration.
</ParamField>

```python theme={null}
# Discover all classes in the com.ardupilot package
classes = await session.jvm.list_classes(prefix="com.ardupilot")
for cls in classes:
    print(cls)
# com.ardupilot.GCS
# com.ardupilot.WaypointManager
# com.ardupilot.MAVLinkConnection
# com.ardupilot.telemetry.TelemetryLogger
```

<Tip>
  Use `list_classes()` once during development to discover the exact class names and field spellings you need. Once you have them, use `read_field()` and `invoke()` in production code — `list_classes()` is slower than targeted field reads.
</Tip>

## Error Reference

| Exception        | Code                   | Cause                                                    |
| ---------------- | ---------------------- | -------------------------------------------------------- |
| `NSPActionError` | `jvm_not_attached`     | JVMTI agent not yet loaded — probe is still attaching    |
| `NSPActionError` | `jvm_class_not_found`  | Class is not loaded in the target JVM                    |
| `NSPActionError` | `jvm_method_not_found` | Method name or parameter signature does not match        |
| `NSPActionError` | `jvm_invoke_timeout`   | Method did not return within `timeout_ms`                |
| `NSPActionError` | `jvm_type_error`       | Cannot coerce a Python argument to the expected JVM type |

## Full Example: ArduPilot GCS Telemetry Monitor

```python theme={null}
import asyncio
import nelieo_nsp as nsp
from nelieo_nsp import RuntimeKind

async def ardupilot_monitor():
    async with nsp.NSPClient(api_key="sk_nsp_...") as client:
        print("Waiting for ArduPilot GCS...")
        session = await client.wait_for_app("ArduPilot GCS", timeout=30.0)
        print(f"Connected. Runtime: {session.runtime}  conf={session.confidence:.2f}")

        if session.state.runtime != RuntimeKind.JVM:
            print("Not a JVM application — exiting")
            return

        # Discover classes during initial setup
        classes = await session.jvm.list_classes(prefix="com.ardupilot")
        print(f"Loaded {len(classes)} ArduPilot classes")

        # Wait for probe confidence to stabilize
        if session.confidence < 0.85:
            print("Waiting for probe to warm up...")
            await session.wait_for(
                lambda s: s.confidence >= 0.85,
                timeout=20.0,
                poll_interval=1.0,
            )

        # Real-time telemetry loop
        while True:
            try:
                alt    = await session.jvm.read_field("com.ardupilot.GCS", "altitude")
                mode   = await session.jvm.read_field("com.ardupilot.GCS", "flightMode")
                armed  = await session.jvm.read_field("com.ardupilot.GCS", "isArmed")
                status = await session.jvm.invoke("com.ardupilot.GCS", "getStatusText")

                print(
                    f"Alt={alt:.1f}m  Mode={mode}  "
                    f"Armed={'✓' if armed else '✗'}  "
                    f"Status: {status}"
                )

            except nsp.NSPActionError as e:
                print(f"JVM error [{e.code}]: {e.message}")

            await asyncio.sleep(1.0)

asyncio.run(ardupilot_monitor())
```
