java-index
Health Warn
- License — License: Apache-2.0
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 8 GitHub stars
Code Fail
- fs module — File system access in editors/vscode/package.json
- exec() — Shell command execution in editors/vscode/src/server.ts
- fs.rmSync — Destructive file system operation in editors/vscode/src/server.ts
- os.homedir — User home directory access in editors/vscode/src/server.ts
- process.env — Environment variable access in editors/vscode/src/server.ts
- network request — Outbound network request in editors/vscode/src/server.ts
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Lightweight Java navigation for reviewing agent-written code: LSP for your editor, MCP for your agents, one index across all worktrees.
java-index
Java code navigation for reviewing what your AI agents write.
java-index answers the questions you ask while reading Java code: where is this defined, who
calls it, what implements it, what is this library method's real signature, which config key does
this @Value read? It serves those answers to your editor over the
Language Server Protocol (LSP), and to
agents such as Claude Code over the Model Context Protocol
(MCP), from one shared index per repository.
https://github.com/user-attachments/assets/9984c749-106f-4f31-ac43-a0c9f10f1f90
Contents
- The problem
- How it's different
- java-index vs jdtls
- What you get — in your editor (LSP), for AI agents (MCP)
- Install
- Set up
- How it works
- Classpath
- Configuration
- Limitations
- License
The problem
More and more Java code is written by agents, and the human's job shifts to reviewing it. Review
means navigating: jumping to a definition, finding the callers of a changed method, checking what
implements an interface. A diff view can't do that, so you open the code in an editor.
That editor usually brings a full Java language server such as jdtls, which is built for writing
code: it imports the build and compiles the project to give you errors, completion and
refactorings. For review that is the wrong trade:
- It's heavy for a read-only job. Each project costs a build import and typically gigabytes of
memory, while all you want is cmd+click and find references. - Agents multiply it. Agents often work in parallel, each in its own git worktree. A full
language server treats every worktree as a separate project: another import, another index,
another JVM. - It fights the agent's build. jdtls compiles the project itself, and for Maven projects it
writes to the sametarget/directory the build uses. When an agent runsmvnwhile the
worktree is open in your editor, the two builds run at once over the same output, and you get
failed or flaky builds, stale classes, and a language server busy rebuilding after every one. - The agents themselves navigate blind. They mostly grep, guess at library signatures, and
can't tell which of tensave()methods a call really goes to.
java-index is built for this workflow. It gives your editor fast, accurate navigation across every
worktree of a repository, at a fraction of the cost, and gives your agents the same index as
tools. It never builds or writes anything in your project: it reads sources and jars, keeps its
caches in its own directory, and asks the build tool only for the classpath, once per change to
the build files. It is not a replacement for jdtls or your IDE: keep those for writing code by hand.
How it's different
- It indexes, it doesn't compile. Sources are parsed with
turbine, Google's header compiler, into compact declaration
summaries. Method bodies are resolved lazily from the token stream when you ask about them. - One daemon per repository. Every editor window, agent session and git worktree of a
repository shares one background daemon and one index. Unchanged files, dependency symbols and
library sources are held once; each worktree keeps only its own changes. - Same answers for people and agents. Editors get go-to-definition, references, hover, call
and type hierarchies. Agents get the same knowledge as name-based tools that return compact text
withfile:linelocations.
java-index vs jdtls
jdtls is a full language server for writing Java; java-index only covers reading it. What
each does:
| java-index | jdtls | |
|---|---|---|
| Definition, references, hover, implementations, type and call hierarchy, symbols | ✓ | ✓ |
| Compiler errors and warnings | — | ✓ |
| Completion | — | ✓ |
| Refactoring, quick fixes (beyond imports), formatting | — | ✓ |
| Debugging, running tests | — | ✓ (with extensions) |
| Lombok and other annotation processors | — | ✓ |
| Spring Boot / Quarkus config keys linked to Java | ✓ | — (separate Spring Tools) |
| Tools for agents (MCP) | ✓ | — |
| One shared process for every worktree of a repository | ✓ | — (one per window) |
Writes into your project (target/) |
never | yes, builds alongside yours |
Measured with the benchmark on spring-petclinic, a small Spring Boot
application (Apple M5 Pro, jdtls 1.63.0 with VS Code's default settings, java-index with the
native relay; medians):
| java-index | jdtls | |
|---|---|---|
| Ready to answer, cold / warm start | 1.5 s / 0.4 s | 2.7 s / 1.8 s |
| Memory, one window | 250 MB | 1.8 GB |
| CPU time to open one window, cold | 7 s | 54 s |
| Query latency (17 navigation queries) | 0.3–7.5 ms | 0.6–385 ms |
| Find references on a common method | 2 ms | 385 ms |
| 8 worktrees open at once: all ready | 1.6 s | 7.8 s |
| 8 worktrees open at once: memory | 416 MB | 12.8 GB |
| 8 worktrees open at once: CPU / disk | 11 s / < 1 MB | 428 s / 1.3 GB |
| Correct answers (17 queries per window) | 17 / 17 | 17 / 17 |
| References and implementations, location by location | same as jdtls | (baseline) |
The gap, explicitly. The numbers above come from one small Maven project and 17 queries
chosen by java-index's author; large codebases, Gradle and multi-module builds have not been
benchmarked yet. jdtls is slower and heavier largely because it does more: it compiles the
project, which java-index never does. And where jdtls compiles, java-index approximates:
- Lombok and annotation processors are not supported. turbine does not run them, so members
generated by@Getter,@Data,@Builderand similar don't exist for java-index: calls to them
go unresolved. - Generated sources only after a build. Code under
target/generated-sources(MapStruct,
protobuf, OpenAPI) is indexed once it exists; on a fresh clone it doesn't yet. - Resolution is approximate. Inference that depends on the assignment target falls back to
bounds, reflection and framework wiring are invisible, and when unsure java-index returns
nothing rather than guess. See Limitations. - No diagnostics. It cannot tell you that code doesn't compile; your build or CI does that.
If you write Java by hand, use jdtls or an IDE. If you mostly review what agents write, often in
several worktrees at once, java-index gives you the same navigation answers for a fraction of
the time and memory.
What you get
In your editor (LSP)
| Feature | What you get |
|---|---|
| Go to definition | into your sources, dependency sources jars, the JDK's src.zip, or generated stubs |
| Find references | exact: occurrences that resolve to the same declaration, not just the same name |
| Hover | signature and javadoc, overrides and implementations, usage counts, deprecation with replacement |
| Implementations, type and call hierarchy | subtypes, overrides, supertypes, incoming and outgoing calls |
| Signature help and inlay hints | overloads while typing; parameter names, var and lambda types, member-chain types |
| Code actions | add a missing import, organize imports |
| Diagnostics | uses of @Deprecated APIs, with the suggested replacement |
| Symbols | file outline; classes, methods and fields across sources and libraries |
Spring Boot and Quarkus configuration. Keys in application*.properties and .yml are linked
both ways with the Java that uses them (@Value, @ConfigurationProperties, @ConfigProperty,@ConfigMapping, getProperty("…")). Jump from a key in Java to where it is set in every profile,
or from a config file to the code that reads it. Hover shows the value per profile and, for library
keys such as server.port, the documented type, default and description. Spring's relaxed binding
is honoured.
For AI agents (MCP)
| Tool | What it answers |
|---|---|
java_api |
The real API of a class or member as this project compiles against it, from source, dependency jars or the JDK |
java_callers |
Usages of a method, constructor or field, with receiver types resolved so same-named methods on other types are excluded |
java_implementations |
All subtypes of a type or all overrides of a method |
java_outline |
A file's types and members with resolved signatures and line numbers |
java_find_type |
Classes by name, camel-case humps or qualified suffix |
java_config |
A Spring Boot / Quarkus config key: its value per profile, the Java that uses it, and library docs |
Every tool takes an optional worktree path to query another worktree of the same repository.
Grep is still the right tool for text search. java-index pays off for library APIs (no guessing
signatures), common method names (save, get, execute) and type hierarchies. Its answers are
strong evidence, not proof: reflection and framework wiring are invisible to it.
Install
macOS (Apple Silicon) and Linux, with Homebrew. This repository is its own
tap:
brew tap slgobinath/java-index https://github.com/slgobinath/java-index
brew trust --formula slgobinath/java-index/java-index
brew install java-index
jindex --version
Homebrew refuses formulae from third-party taps until you trust them, hence brew trust. Later
releases install with brew update && brew upgrade java-index.
This installs the jindex command: a small native relay, plus the daemon it starts, which runs on
Homebrew's OpenJDK (set JINDEX_JAVA to use another JDK 21+).
Anywhere else (Intel Macs, Windows, or without Homebrew): download java-index-<version>.jar
from the releases and usejava -jar /path/to/java-index-<version>.jar (Java 21+) wherever this README says jindex.
From source (JDK 21+):
mvn package # target/java-index.jar, runs the tests
JAVA_HOME=/path/to/graalvm-25 mvn -Pnative package # also target/jindex-relay, the native relay
target/jindex-relay works like jindex: it finds java-index.jar next to itself (or in../target, ../lib) and starts the daemon with $JINDEX_JAVA, $JAVA_HOME/bin/java or java
from the PATH. Each editor window or agent session runs one relay; the native one uses about
12 MB instead of a JVM's 60–80 MB.
Set up
jindex starts (or connects to) the repository's daemon:
jindex # LSP over stdio, for editors (same as: jindex lsp)
jindex mcp # MCP over stdio, for agents (--root DIR to override the working directory)
jindex --help # usage; --version prints the version
Run bare, jindex waits for an editor to speak LSP on stdin, so it looks like it hangs in a
terminal; that is expected. Press Ctrl-D to leave.
Claude Code (run in your repository):
claude mcp add java-index -- jindex mcp
Neovim (0.11+):
vim.lsp.config('java-index', {
cmd = { 'jindex' },
filetypes = { 'java' },
root_markers = { '.git', 'pom.xml', 'build.gradle', 'build.gradle.kts' },
})
vim.lsp.enable('java-index')
Helix (languages.toml):
[language-server.java-index]
command = "jindex"
[[language]]
name = "java"
language-servers = ["java-index"]
Emacs (eglot): (add-to-list 'eglot-server-programs '(java-mode "jindex"))
Zed: Zed's Java extension hard-wires jdtls, so editors/zed holds a small
extension that adds java-index as a Java server. Keep the Java extension for syntax highlighting,
install editors/zed with zed: install dev extension, then in settings.json:
"languages": {
"Java": { "language_servers": ["java-index", "!jdtls", "..."] }
}
The extension runs jindex from the PATH; to use the jar instead, add"lsp": { "java-index": { "binary": { "path": "java", "arguments": ["-jar", "/path/to/java-index.jar"] } } }.
VS Code: editors/vscode holds an extension that runs jindex from the
PATH or, if it isn't there, downloads the matching release from GitHub (the native relay on Apple
Silicon and Linux, the jar elsewhere; the daemon still needs Java 21+). Installjava-index-<version>.vsix from the releases
with code --install-extension java-index-<version>.vsix. Disable the Red Hat Java extension in
workspaces you review with java-index, or you get every answer twice. Settings are in
its README.
Sublime LSP / Kate: register a custom stdio server running jindex for Java files.
How it works
editor ──stdio──► jindex (relay) ──unix socket──► daemon (one per repository)
editor ──stdio──► jindex (relay) ─────────────┤
agent ──stdio──► jindex mcp (relay) ─────────────┘
- Routing. Each client runs a small relay. It finds the repository's git common dir (shared by
all its worktrees) and connects to that repository's daemon, starting it if needed. - Shared index. Each source file becomes a declaration summary keyed by content hash. The first
worktree indexed becomes the shared base snapshot; other worktrees store only the files that
differ, plus unsaved editor buffers. - Dependencies. Jars are read lazily with turbine's bytecode binder, shared across worktrees
with the same build files. Definitions in libraries open their-sources.jar, the JDK'ssrc.zip, or a stub generated from the class file. - Method bodies. Turbine skips method bodies, so the cursor is resolved from tokens: locals,
var, lambdas, anonymous classes and member chains with generic substitution, overload selection
and lambda-based inference. - References. Each file summary carries a Bloom filter of its identifiers, so a search reads only
files that may mention the name, then keeps the occurrences that resolve to the same declaration. - Resources. The relay only forwards bytes; the daemon does the work. It uses a
shrinkable heap, compacts when quiet and exits after 15 idle minutes.
Classpath
Resolved per worktree, in this order:
initializationOptions.classpath: a list of jars or jar directories..java-index/classpathin the worktree: one entry per line.- Maven (
pom.xml, uses./mvnwif present):dependency:build-classpath. - Gradle (
build.gradle[.kts], uses./gradlew): an init script that prints the compile classpaths.
Results are cached, keyed by the content of the build files, and recomputed when a build file changes.
Configuration
| Variable | Default | Purpose |
|---|---|---|
JINDEX_HOME |
$XDG_CACHE_HOME/java-index or ~/.cache/java-index |
sockets, daemon logs (<key>.log), extracted sources, cached classpaths |
JINDEX_DAEMON_OPTS |
-Xmx1g |
extra JVM flags for the daemon |
JINDEX_IDLE_MINUTES |
15 |
daemon exits after this long with no connections |
JINDEX_JAVA |
java |
JDK (21+) the relay starts the daemon with |
LSP initialization options:
| Option | Default | |
|---|---|---|
classpath |
build tool | see Classpath |
inlayHints.parameterNames |
"literals" |
"all" or "none" |
inlayHints.types |
true |
var, lambda parameter and chain type hints |
deprecations |
true |
mark uses of deprecated APIs |
java -jar java-index.jar lsp --no-daemon and … mcp --no-daemon serve in-process without a daemon (the jar only; the native relay always relays), which is
useful for debugging.
Limitations
Resolution is deliberately approximate. Generic methods are inferred from their arguments and
lambda parameters from the expected functional interface, so stream().map(...).filter(...) andforEach(item -> item.save()) resolve. Inference that needs the assignment target
(collect(Collectors.toList())) falls back to the type variable's bound. When java-index is unsure
it returns nothing rather than guessing. References are exact matches on the resolved declaration:
a call through a subclass that overrides a method counts for the override (the MCP java_callers
tool reports both, in separate groups).
License
Licensed under the Apache License, Version 2.0. The built jar bundles third-party
libraries under Apache 2.0, EPL 2.0 and MIT; see NOTICE.
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found