Skip to content

feat(jdtls): support upstream JDTLS via jdtls_path/lombok_path settings#1415

Merged
MischaPanch merged 6 commits intooraios:mainfrom
a-simeshin:feat/jdtls-path-override
Apr 28, 2026
Merged

feat(jdtls): support upstream JDTLS via jdtls_path/lombok_path settings#1415
MischaPanch merged 6 commits intooraios:mainfrom
a-simeshin:feat/jdtls-path-override

Conversation

@a-simeshin
Copy link
Copy Markdown
Contributor

@a-simeshin a-simeshin commented Apr 25, 2026

Closes #1414

Activation

ls_specific_settings:
  java:
    jdtls_path: \"/opt/homebrew/Cellar/jdtls/.../libexec\"   # required
    lombok_path: \"/path/to/lombok-1.18.X.jar\"              # required (always attached as -javaagent)
    # java_home: \"/opt/openjdk@21\"                          # optional, falls back to JAVA_HOME / PATH

Both paths set → upstream mode (no downloads). Exactly one set → init fails with a clear message. Neither set → unchanged behaviour, vscode-java VSIX is downloaded as today (default code path is byte-for-byte identical).

Resolution details

  • jdtls_path expects the upstream layout (plugins/, config_<platform>/, features/).
  • The main org.eclipse.equinox.launcher_*.jar is located via regex glob, excluding the platform-specific native fragments (*.cocoa.macosx.*, *.gtk.linux.*, *.win32.win32.*).
  • The platform-specific config_<platform>/ is auto-selected from PlatformUtils.get_platform_id().
  • JDK home is discovered by querying the JVM via java -XshowSettings:properties -version. This is the single source of truth and works around the macOS /usr/bin/java stub case, where parent.parent of the locator yields /usr (system root) instead of the real JDK home; the JVM correctly reports its own java.home regardless.
  • JDK major version is validated >= 21 from the same JVM output.
  • Lombok agent is always attached (-javaagent:<lombok_path>); harmless when the project doesn't use Lombok.
  • IntelliCode bundle is skipped — it's an ML completion ranker, irrelevant to Serena's symbol-tools workflow.
  • Gradle distribution is not downloaded — Buildship's default discovery rules apply: ./gradlew in the project, or system Gradle via ~/.gradle/. Maven projects work via JDTLS-bundled m2e (Maven Embedder).

Workspace hash bugfix (included)

Independent of the main feature, EclipseJDTLS.create_launch_command() previously derived the workspace directory hash only from the project path. The config_path directory inside the workspace is copied from the readonly OSGi config on first launch and reused thereafter. When the JDTLS launcher is upgraded (default vscode_java_version bump, or switching modes), the stale config still references bundles from the previous launcher version, and the new launcher hangs on initialize until the request times out.

This PR includes the launcher jar path in the workspace-hash input so a launcher upgrade or mode switch produces a fresh config_path. Happy to split this into a separate PR if preferred.

Tests

A new file test/solidlsp/java/test_jdtls_path_resolution.py covers the resolution helpers without requiring a real JDTLS install or Java runtime — subprocess interactions and platform detection are mocked, JDTLS layouts are synthesised in tmp_path. Coverage:

  • _resolve_launcher_jar — picks main launcher, excludes native fragments, selects highest version, errors on empty plugins
  • _resolve_config_dir — parameterised by platform, errors on missing / unsupported
  • _inspect_java — parses Temurin 21 and OpenJDK 17 outputs, errors on missing java.home / version / subprocess failure
  • _resolve_system_jdk — priority chain (java_home setting > JAVA_HOME > which java), version validation, the macOS /usr/bin/java stub case
  • _setup_from_existing_install — happy path (gradle / intellicode None as expected) and validation errors
  • _setup_runtime_dependencies switch — both/one/neither path-pair set

Default-mode integration tests in test/solidlsp/java/test_java_basic.py are unchanged and still cover the vscode-java VSIX path end-to-end.

Local checks: ruff check, ruff format --check, mypy (src + test), pytest on the new file all pass.

Open design questions

  • Activation trigger. Currently implicit (both paths set ⇒ activate). An explicit boolean (use_upstream_jdtls: true + paths) would be more obvious but adds one more setting. Happy to switch if maintainers prefer.
  • Workspace-hash change. Included in this PR since it's tightly coupled to mode-switching, but logically a small standalone bugfix; can be split out if preferred.

a-simeshin and others added 3 commits April 25, 2026 21:20
Add an offline-friendly Java mode for restricted-network/corporate environments.
When both ls_specific_settings.java.jdtls_path and lombok_path are set, Serena
uses an existing upstream JDTLS install (e.g. brew install jdtls or extracted
jdt-language-server-*.tar.gz from download.eclipse.org) and the system JDK 21+
instead of downloading the ~500 MB platform-specific vscode-java VSIX bundle.

The default vscode-java VSIX path is unchanged when neither setting is provided.

Resolution details:
- jdtls_path expects upstream layout (plugins/, config_<platform>/, features/)
- The main equinox.launcher jar is located via regex glob, excluding native fragments
- Platform-specific config_<platform>/ is auto-selected by PlatformUtils
- JDK home is discovered by querying the JVM via 'java -XshowSettings:properties
  -version' (single source of truth — works around macOS /usr/bin/java stub which
  can't be resolved via path traversal)
- JDK major version is validated >= 21 from the same JVM output
- Lombok agent is always attached (lombok_path is mandatory in this mode)
- IntelliCode bundle and Gradle distribution are skipped (irrelevant for Serena's
  symbol-tools workflow; Maven works via JDTLS-bundled m2e, Gradle via ./gradlew
  or system install through Buildship default discovery)

Workspace hash now incorporates the launcher jar path so plan A and the default
mode use distinct workspace directories — prevents stale OSGi configs from a
prior JDTLS version blocking startup.

Refs: oraios#1414
Cover the offline-mode helpers added in the previous commit without requiring
a real JDTLS install or Java runtime — subprocess interactions and platform
detection are mocked, JDTLS layouts are synthesised in tmp_path.

30 tests across:
- _resolve_launcher_jar — picks main launcher, excludes native fragments,
  selects highest version, errors on empty plugins
- _resolve_config_dir — parameterised by platform, errors on missing /
  unsupported
- _inspect_java — parses Temurin 21 and OpenJDK 17 outputs, errors on missing
  java.home / version / subprocess failure
- _resolve_system_jdk — priority chain (java_home setting > JAVA_HOME > which
  java), version validation, the macOS /usr/bin/java stub case where the JVM
  reports a different real java.home
- _setup_from_existing_install — happy path (gradle/intellicode None as
  expected) and validation errors
- _setup_runtime_dependencies switch — both/one/neither path-pair set

Refs: oraios#1414
Windows resolver looks for bin/java.exe, but the helper created bin/java
unconditionally — two tests failed on windows-latest. Switch the helper
and assertions to a _JAVA_EXE_NAME constant derived from platform.system().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@a-simeshin
Copy link
Copy Markdown
Contributor Author

a-simeshin commented Apr 26, 2026

Looks like there are infra problems with CI, unrelated to code changes:

  • ubuntu-latest: R languageserver package missing on the runner, 5 errors in test/solidlsp/r/test_r_basic.py (RuntimeError: R languageserver package is not installed).
  • windows-latest: GitHub Releases returned 502 Bad Gateway when downloading the bundled JDTLS runtime (vscode-java/releases/download/v1.42.0/java-win32-x64-1.42.0-561.vsix) - test_find_symbol_stable[java-Model-...] failed during LS init.

@opcode81 can I ask about rerun jobs? Maybe it would help

@MischaPanch
Copy link
Copy Markdown
Contributor

@a-simeshin I restarted the jobs

@a-simeshin
Copy link
Copy Markdown
Contributor Author

a-simeshin commented Apr 26, 2026

@a-simeshin I restarted the jobs

Well at least Win tests are pass now, but pytest.yml uses runs-on: ubuntu-latest, which GitHub now
resolves to ubuntu-24.04 (image release ubuntu24/20260413.86, dated 2026-04-13).

On this image, the Install R language server step can no longer build the
R fs package from source because the system library libuv1-dev is missing:

* installing *source* package ‘fs’ ...
Package libuv was not found in the pkg-config search path.
fatal error: uv.h: No such file or directory
ERROR: configuration failed for package ‘fs’

That cascades through pkgloadroxygen2languageserver, which is why test/solidlsp/r/test_r_basic.py aborts with:

RuntimeError: R languageserver package is not installed

Why

No commit in oraios/serena switched the image — git log -S "ubuntu-22.04" and -S "ubuntu-24.04" against
.github/workflows/ come up empty. The workflow has always pointed at ubuntu-latest. The change happened on GitHub's side: either the migration of ubuntu-latest from 22.04 to 24.04 (tracked in
actions/runner-images#10636), or a recent 24.04 image release, that dropped libuv1-dev from the preinstalled set / triggered CRAN to fall back to source builds for fs on this R/Ubuntu combo. macOS and Windows are unaffected.

I can fix it with

In .github/workflows/pytest.yml, before the Install R language server step place:

- name: Install R sysdeps
  if: runner.os == 'Linux'
  run: sudo apt-get update && sudo apt-get install -y libuv1-dev

Alternative

Keep the current step but build fs with bundled libuv (slower, but no apt):

- name: Install R language server
  env:
    USE_BUNDLED_LIBUV: "1"
  run: Rscript -e "install.packages('languageserver', repos='https://cloud.r-project.org')"

Happy to open a separate PR with the workflow fix if it would be helpful

@MischaPanch
Copy link
Copy Markdown
Contributor

Thanks, the R failure has been around for a few weeks, unrelated to changes here. Yes, a PR to fix the R install failure on ubuntu would be appreciated, thank you!

@MischaPanch
Copy link
Copy Markdown
Contributor

Thanks for the PR @a-simeshin ! Pls also extend the changelog and our documentation of the java options in 050_configuration.md, in the advanced options section.

Generally, I highly recommend using our jetbrains plugin for java development. Apart from being more stable, having more features and introducing less overhead, using the plugin also supports this project.

- CHANGELOG.md: add entry under "Language Servers" describing the new
  upstream JDTLS mode (jdtls_path/lombok_path/java_home settings).

- docs/02-usage/050_configuration.md (Java section):
  - Add "When to use which mode" decision helper before the settings
    table, listing concrete reasons to pick the upstream mode
    (restricted networks, on-disk footprint, existing JDTLS install,
    security policy).
  - Promote the JDK 21+ requirement to a dedicated paragraph and
    document the exact JDK resolution order
    (java_home setting -> JAVA_HOME env -> first java on PATH).
  - Note that vscode-java-only settings (gradle_version,
    vscode_java_version, intellicode_*) are silently ignored when
    upstream mode is active, so users don't expect them to take effect.
@a-simeshin
Copy link
Copy Markdown
Contributor Author

Thanks for the PR @a-simeshin ! Pls also extend the changelog and our documentation of the java options in 050_configuration.md, in the advanced options section.

Generally, I highly recommend using our jetbrains plugin for java development. Apart from being more stable, having more features and introducing less overhead, using the plugin also supports this project.

Done! Unfortunately, corporate terms and conditions impose restrictions on many JetBrains plugins. Otherwise I'd really rather not have to configure JDTLS by myself.

@MischaPanch
Copy link
Copy Markdown
Contributor

MischaPanch commented Apr 26, 2026

Ah, that's a shame. Anything we can do to let the plugin be accepted by your IT department @a-simeshin ? We could give a presentation or sell the plugin via a different channel than the Jetbrains marketplace. The plugin runs fully locally, doesn't have any telemetry or anything security critical. From an IT sec perspective, allowing to run Serena but disallowing the plugin doesn't make sense

Feel free to contact us at info@oraios-software.de

@a-simeshin
Copy link
Copy Markdown
Contributor Author

Ah, that's a shame. Anything we can do to let the plugin be accepted by your IT department @a-simeshin ? We could give a presentation or sell the plugin via a different channel than the Jetbrains marketplace. The plugin runs fully locally, doesn't have any telemetry or anything security critical. From an IT sec perspective, allowing to run Serena but disallowing the plugin doesn't make sense

Feel free to contact us at info@oraios-software.de

I see a very shady situation here for me and others within the corporate rules.

Usually I have to submit any opesource code for internal review, justify down to the last line why it's needed, and then maybe in about six months I'll be able to use it. I waited six months to use IDE themes, I'm not kidding 🥲

Furthermore sometimes companies forks JetBrains IDE and creates its own closed build with closed plugins.
Therefore for me there's zero chance of using any code outside of open source, including a JetBrains plugin.

This is why I actually switched to Zed and use Serena as an MCP with an approved jdtls version from Eclipse. Someone had already submitted jdtls earlier, after going through a hell of bureaucratic hassle. So this fix is the only one chance to use Serena officially on my office projects.

Also I don't know if you're aware of this or not, but jdtls doesn't work correctly with getters and setters generated via lombok, but in your Jetbrains plugin everything works fine.

Copy link
Copy Markdown
Contributor

@MischaPanch MischaPanch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @a-simeshin, this is close to be merged, just minor questions.

Sorry to hear about your corporate restrictions, sounds really inconvenient

Comment thread src/solidlsp/language_servers/eclipse_jdtls.py Outdated
Comment thread docs/02-usage/050_configuration.md
The previous change unconditionally mixed the JDTLS launcher jar path
into the workspace md5, which would have invalidated every existing
JDTLS workspace on Serena upgrade and forced a one-time cold reindex
for users who never opted into upstream-JDTLS mode.

Make the launcher path part of the hash only when `jdtls_path` is
explicitly set, so default-route users get the exact pre-upstream
format (`md5(repository_root_path)`) and reuse their caches as before.
Upstream mode keeps the launcher path in the hash to isolate it from
the default workspace and from other upstream installations.

Extract the hash computation into a small static helper and add unit
tests covering both branches, the backwards-compatibility contract,
and default/upstream isolation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@MischaPanch MischaPanch merged commit a3565ef into oraios:main Apr 28, 2026
7 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support explicit paths for JDTLS & JDK & Lombok to enable offline / corporate-network usage

2 participants