DEV Community

Cover image for Give your AI coding agent an IDE for Java and Kotlin jars (with jdx)
Mohammad Mostafa Dastjerdi
Mohammad Mostafa Dastjerdi

Posted on

Give your AI coding agent an IDE for Java and Kotlin jars (with jdx)

AI coding agents are good at writing JVM code. Reading it is another story.

Ask an agent what a method in one of your dependencies does and watch the transcript: it unzips the jar, runs javap, greps the output, and then reads an entire source file into its context window. javap gives it erased signatures and raw opcodes, so it fills the gaps by guessing.

In this tutorial we'll give the agent the answers an IDE gives a human, using jdx, an open-source CLI and MCP server I built for exactly this. Every output below comes from the jdx quickstart, which is generated by actually running the commands.

What you need

  • A Java 21+ runtime (Linux, macOS or Windows)
  • An MCP-capable agent if you want the integration step (Claude Code, Cursor, Codex CLI, GitHub Copilot CLI, OpenCode, Cline, Kilo Code, Antigravity, or anything else that speaks MCP)

1. Install

curl -fsSL https://raw.githubusercontent.com/MohammadMD1383/jdx/main/install-release.sh | bash
jdx version
Enter fullscreen mode Exit fullscreen mode

It installs per user (no root) into ~/.local/share/jdx and links ~/.local/bin/jdx. On Windows, use the PowerShell installer from the installation guide. jdx doctor checks your environment if anything looks off.

Releases are unsigned, so macOS Gatekeeper and Windows SmartScreen will warn on first run. The installation guide shows how to clear it.

2. Ask the JDK something, with zero setup

The running JDK is always on the classpath:

$ jdx members java.util.HashMap --limit 5
members of java.util.HashMap
constructors declared on java.util.HashMap:
  constructor public HashMap()
  constructor public HashMap(int initialCapacity)
  constructor public HashMap(int initialCapacity, float loadFactor)
  constructor public HashMap(java.util.Map<? extends K, ? extends V> m)
methods declared on java.util.HashMap:
  method public V compute(K key, java.util.function.BiFunction<? super K, ? super V, ? extends V> remappingFunction)
+ 7 from java.lang.Object (--from java.lang.Object to expand)
5 of 53 members shown (--limit 53 to see more)
source: java.base (jrt)
Enter fullscreen mode Exit fullscreen mode

Two things matter here for an agent. The output is bounded, and the footer says exactly what was cut and how to get the rest. And the source: line says where the answer came from.

Now read one method, and only that method:

$ jdx body 'java.util.HashMap#get(java.lang.Object)'
java.util.HashMap#get(java.lang.Object)
  source: src.zip · java.base/java/util/HashMap.java:562-565
    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(key)) == null ? null : e.value;
    }
next: jdx show java.util.HashMap
Enter fullscreen mode Exit fullscreen mode

Quote references in your shell: #, ( and $ are shell syntax.

3. Ask about a library by its Maven coordinate

--coord resolves a library from your local Gradle and Maven caches. --fetch allows downloading it (and its -sources.jar) from Maven Central once:

$ jdx body 'com.google.gson.Gson#toJson(Object)' --coord com.google.code.gson:gson:2.14.0
com.google.gson.Gson#toJson(java.lang.Object)
  source: gson-2.14.0-sources.jar · com/google/gson/Gson.java:542-547
  public String toJson(Object src) {
    if (src == null) {
      return toJson(JsonNull.INSTANCE);
    }
    return toJson(src, src.getClass());
  }
next: jdx show com.google.gson.Gson
Enter fullscreen mode Exit fullscreen mode

(Gson is a multi-release jar, so on JDK 21 jdx also prints a warning MULTI_RELEASE_VARIANT line; I've trimmed it from the outputs in this article.)

If there is no sources jar, jdx decompiles the method with Vineflower and marks the output ⚠ reconstructed, so the agent knows it's reading a reconstruction. (--engine javap gives raw bytecode instead.)

4. Let it refuse to guess

toJson has eight overloads. jdx won't pick one:

$ jdx body 'com.google.gson.Gson#toJson' --coord com.google.code.gson:gson:2.14.0
ambiguous: 8 candidates for com.google.gson.Gson#toJson
  com.google.gson.Gson#toJson(com.google.gson.JsonElement)
  com.google.gson.Gson#toJson(com.google.gson.JsonElement, com.google.gson.stream.JsonWriter)
  com.google.gson.Gson#toJson(com.google.gson.JsonElement, java.lang.Appendable)
  com.google.gson.Gson#toJson(java.lang.Object)
  com.google.gson.Gson#toJson(java.lang.Object, java.lang.Appendable)
  com.google.gson.Gson#toJson(java.lang.Object, java.lang.reflect.Type)
  com.google.gson.Gson#toJson(java.lang.Object, java.lang.reflect.Type, com.google.gson.stream.JsonWriter)
  com.google.gson.Gson#toJson(java.lang.Object, java.lang.reflect.Type, java.lang.Appendable)
hint: re-run with one of the refs above
[exit 2]
Enter fullscreen mode Exit fullscreen mode

Exit code 2 means "ambiguous", and every candidate is a paste-ready reference. An agent can branch on the exit code without parsing prose, and the next call is a copy-paste, not a reconstruction. Other outcomes (not found, no index, bad usage) have their own exit codes, and every command has --json.

5. Navigate: hierarchy and callers

$ jdx hierarchy com.google.gson.JsonElement --coord com.google.code.gson:gson:2.14.0
hierarchy of 'com.google.gson.JsonElement'
  supertypes
    extends java.lang.Object  java.base
  subtypes in workspace (4)
    class com.google.gson.JsonArray  gson-2.14.0.jar
    class com.google.gson.JsonNull  gson-2.14.0.jar
    class com.google.gson.JsonObject  gson-2.14.0.jar
    class com.google.gson.JsonPrimitive  gson-2.14.0.jar
Enter fullscreen mode Exit fullscreen mode
$ jdx callers 'com.google.gson.JsonParser#parseReader(com.google.gson.stream.JsonReader)' --coord com.google.code.gson:gson:2.14.0
callers of 'com.google.gson.JsonParser#parseReader(com.google.gson.stream.JsonReader)'
  call com.google.gson.JsonParser#parse(com.google.gson.stream.JsonReader)  gson-2.14.0.jar
  call com.google.gson.JsonParser#parseReader(java.io.Reader)  gson-2.14.0.jar
Enter fullscreen mode Exit fullscreen mode

--depth N follows the call graph transitively (cycle-safe). jdx usages and jdx implementors work across every jar on the classpath, not just one. More in Usages, hierarchy & calls.

6. Ask what a dependency upgrade breaks

$ jdx diff com.google.code.gson:gson:2.11.0 com.google.code.gson:gson:2.14.0 --fetch
api diff com.google.code.gson:gson:2.11.0 -> com.google.code.gson:gson:2.14.0 (public surface)
info  TYPE_ADDED  com.google.gson.internal.GsonTypes
...
info  MEMBER_ADDED  com.google.gson.stream.JsonReader#setNestingLimit(int)
info  FIELD_CONSTANT_VALUE_CHANGED  com.google.gson.internal.GsonBuildConfig#VERSION: "2.11.0" -> "2.14.0"
0 breaking · 0 suspicious · 17 informational · 2 types added · 0 types removed
Enter fullscreen mode Exit fullscreen mode

Every finding names a rule (MEMBER_REMOVED, RETURN_TYPE_CHANGED, KOTLIN_NULLABILITY_CHANGED, …) at breaking, suspicious or info. Add --fail-on breaking and it becomes a CI gate. See API diff for upgrades.

7. Name your classpath once

jdx ws create gson --coord com.google.code.gson:gson:2.14.0
jdx -w gson usages com.google.gson.TypeAdapter
jdx ws use gson        # make it the default
Enter fullscreen mode Exit fullscreen mode

Inside a Gradle or Maven project you often need nothing: jdx discovers the project's sources and dependency jars from the local caches, without running the build.

8. Hand it to your agent

jdx setup --agent claude-code --scope project   # then restart Claude Code
# or: cursor, codex, copilot, opencode, cline, kilo, antigravity, windsurf, aider
Enter fullscreen mode Exit fullscreen mode

Any other MCP client takes one line of config:

{"mcpServers":{"jdx":{"command":"jdx","args":["mcp"]}}}
Enter fullscreen mode Exit fullscreen mode

The MCP server exposes each query as a typed tool (jdx_members, jdx_body, jdx_usages, jdx_callers, jdx_diff, …) and keeps the index warm, so queries take about 20 ms instead of about 250 ms for a cold CLI call. If you'd rather use the CLI from your agent, jdx help --agent prints a compact block to paste into CLAUDE.md or AGENTS.md.

Per-agent setup pages: Claude Code, Cursor, Codex CLI, all integrations.

New in v1.7.0: the questions IDEs don't answer

Agents also ask things an IDE never made easy. v1.7.0 adds commands for them (see the command reference for every flag):

  • jdx literals — search string constants (LDC loads and ConstantValue fields) and .properties keys/values across the workspace: "where does this log message / resource key come from?"
  • jdx services — META-INF/services SPI providers: "who provides com.foo.Spi?"
  • jdx modules — JPMS module-info (requires / exports / opens / uses / provides).
  • jdx manifest and jdx resources — MANIFEST.MF attributes and non-class resources (--cat prints a text resource).
  • jdx annotated — classes and members carrying an annotation, with recipes such as deprecated, forRemoval, test and bean; jdx usages --where filters on annotation values.
  • callers is now override-aware by default (--exact for invoke-owner matching), bytecode usages carry file:line, and usages --context shows inline snippets.
  • Kotlin nullability is rendered from @Metadata, and there's a PowerShell installer for Windows.

Does it actually save context?

On one task from the README, "list Gson's API, then show only toJson(Object)" on gson-2.14.0:

Approach Calls Output in context
unzip + javap + grep + sed 5 ~17.5 KB (~4.4k tokens), and head -200 missed the method entirely
jdx members + jdx body 2 ~4.6 KB (~1.2k tokens)

The method body alone is 267 bytes, against 60,590 for javap -c and 58,251 for the full Gson.java. That's one task, not a benchmark suite, but you can rerun it yourself with sh docs/bench-compare.sh from the repo.

A note on safety

jdx parses bytecode with ASM and never loads the classes it inspects, so no static initialiser or other code from those jars runs. It only touches the network when you pass --fetch, run jdx upgrade, or run jdx kotlin install.

Kotlin

Kotlin binaries are read through @Metadata, so the agent sees nullability, suspend functions, properties, default arguments and @JvmName mappings instead of the JVM projection. Reading Kotlin sources needs a one-time jdx kotlin install. Details in Reading code.

Wrapping up

jdx is free and open source (Apache-2.0). Start with the quickstart, browse the docs, and if it gives your agent a wrong answer, please open an issue with the command and what you expected.

Source: github.com/MohammadMD1383/jdx

Top comments (0)