[BUG] jdtls-lsp: workspace data dir keyed on cwd basename — all same-named packages/worktrees collide onto one cache, forcing constant re-imports

Open 💬 0 comments Opened Jun 10, 2026 by TrevorBurnham

Bug Description

The jdtls-lsp plugin launches jdtls with no -data argument, so jdtls falls back to its default workspace-cache location — which is derived from only the basename of the working directory:

# jdtls.py (Homebrew jdtls 1.58.0)
cwd_name = os.path.basename(os.getcwd())
jdtls_data_path = os.path.join(cachedir, "jdtls-" + sha1(cwd_name.encode()).hexdigest())

Any two project directories that share a basename therefore map to the same jdtls data dir and fight over one cache. Each time you switch between them, jdtls re-imports the workspace from scratch (cold start, full re-index). This is common and painful in two setups:

  • Git worktrees — multiple worktrees of the same package all named src/MyService.
  • Monorepos / multi-root build systems where many leaf packages share conventional names.

In my case, 4 worktrees all containing src/Package collapse to one cache:

1cb969fa23a029d0f0581916fe3f256a07964b33  <-  /…/original-package/
1cb969fa23a029d0f0581916fe3f256a07964b33  <-  /…/ungate-documentation/
1cb969fa23a029d0f0581916fe3f256a07964b33  <-  /…/hide-negative-values/
1cb969fa23a029d0f0581916fe3f256a07964b33  <-  /…/nameprefix-conditional/

Environment

  • macOS (Apple Silicon), Claude Code 2.1.170
  • jdtls-lsp@claude-plugins-official 1.0.0
  • jdtls 1.58.0 (Homebrew), Java 21

Reproduction

  1. Create two directories with the same basename in different parents, each a valid Java project (e.g. ~/a/MyService and ~/b/MyService).
  2. Open Claude Code in the first; run any LSP op so jdtls imports it.
  3. Open Claude Code in the second; run an LSP op.
  4. Observe both share ~/Library/Caches/jdtls/jdtls-<sha1(basename)>, and the second import evicts/re-imports the first. Switching back re-imports again.

Suggested fix

Have the plugin pass an explicit -data keyed on the full project path, using the args field (already in the lspServers schema) and the ${CLAUDE_PROJECT_DIR} substitution:

"jdtls": {
  "command": "jdtls",
  "args": ["-data", "${HOME}/Library/Caches/jdtls/claude${CLAUDE_PROJECT_DIR}"],
  "extensionToLanguage": { ".java": "java" },
  "startupTimeout": 120000
}

This gives each distinct workspace its own cache, eliminating cross-worktree thrash, and is verified working — jdtls.py only defaults -data when absent, so a passed value wins. (A hashed path would avoid deep nesting if preferred.)

Related (distinct issues): #64536 (per-subagent jdtls instances → RAM), #37431 (null rootUri/workspaceFolder, closed not-planned).

View original on GitHub ↗