oraios/serena · error · SolidLSPException

Invalid jdtls_path '{jdtls_path}': 'plugins/' directory not

Error message

Invalid jdtls_path '{jdtls_path}': 'plugins/' directory not found.
Expected upstream JDTLS layout (plugins/, config_<platform>/, features/) at the root. If you pointed at a vscode-java extension, use '<extension>/server' instead.

What it means

This SolidLSPException is raised when jdtls_path exists but lacks a 'plugins/' subdirectory, meaning it does not match the upstream JDTLS layout (plugins/, config_<platform>/, features/). A very common cause is pointing jdtls_path at the vscode-java extension root instead of its 'server' subdirectory.

Source

Thrown at src/solidlsp/language_servers/eclipse_jdtls.py:483

            and the system-installed JDK. No downloads are performed.

            :param jdtls_path: absolute path to the JDTLS root (containing ``plugins/`` and ``config_<platform>/``)
            :param lombok_path: absolute path to the Lombok jar (mandatory; agent is always attached)
            :param custom_settings: language-server-specific settings from ls_specific_settings.java
            :return: populated RuntimeDependencyPaths with ``gradle_path`` set to ``None``
            """
            # validate jdtls_path structure (root + plugins dir)
            jdtls_root = Path(jdtls_path)
            if not jdtls_root.is_dir():
                raise SolidLSPException(
                    f"Provided jdtls_path '{jdtls_path}' is not an existing directory.\n"
                    f"Fix: extract jdt-language-server-*.tar.gz from "
                    f"https://download.eclipse.org/jdtls/snapshots/ or run 'brew install jdtls', "
                    f"then set ls_specific_settings.java.jdtls_path to the extracted directory."
                )
            plugins_dir = jdtls_root / "plugins"
            if not plugins_dir.is_dir():
                raise SolidLSPException(
                    f"Invalid jdtls_path '{jdtls_path}': 'plugins/' directory not found.\n"
                    f"Expected upstream JDTLS layout (plugins/, config_<platform>/, features/) at the root. "
                    f"If you pointed at a vscode-java extension, use '<extension>/server' instead."
                )

            # resolve the main equinox launcher jar (excluding platform-specific native fragments)
            launcher_jar = EclipseJDTLS.DependencyProvider._resolve_launcher_jar(plugins_dir)

            # resolve the platform-specific config directory under jdtls_root
            config_dir = EclipseJDTLS.DependencyProvider._resolve_config_dir(jdtls_root)

            # validate lombok jar exists
            if not Path(lombok_path).is_file():
                raise SolidLSPException(
                    f"Provided lombok_path '{lombok_path}' does not exist or is not a file.\n"
                    f"Fix: download lombok jar from https://projectlombok.org/downloads/ or use the one "
                    f"from your local Maven cache (~/.m2/repository/org/projectlombok/lombok/<version>/lombok-<version>.jar)."
                )

View on GitHub (pinned to 7fcbca7e62)

Solutions

  1. If pointing at a vscode-java extension, append '/server' to the path (e.g. '<ext_dir>/server').
  2. Inspect jdtls_path for a nested directory containing plugins/, config_<platform>/, features/ and point at that root.
  3. Re-extract the official JDTLS tarball so the layout sits directly under jdtls_path.
  4. On macOS with brew, use the libexec directory of the jdtls formula as jdtls_path.

Example fix

// before
"jdtls_path": "/Users/me/.vscode/extensions/redhat.java-1.30.0"
// after
"jdtls_path": "/Users/me/.vscode/extensions/redhat.java-1.30.0/server"
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path
root = Path(jdtls_path).expanduser()
if root.is_dir() and not (root / 'plugins').is_dir():
    server = root / 'server'  # vscode-java extension layout
    if (server / 'plugins').is_dir():
        settings['ls_specific_settings']['java']['jdtls_path'] = str(server)

Type guard

def has_upstream_jdtls_layout(path: str) -> bool:
    from pathlib import Path
    root = Path(path).expanduser()
    return root.is_dir() and (root / 'plugins').is_dir() and (root / 'features').is_dir()

Try / catch

try:
    ls = SolidLSP(java_config)
except SolidLSPException as e:
    if "'plugins/' directory not found" in str(e):
        cfg = java_config['ls_specific_settings']['java']
        cfg['jdtls_path'] = str(Path(cfg['jdtls_path']) / 'server')
        ls = SolidLSP(java_config)
    else:
        raise

Prevention

When it happens

Trigger: _setup_from_existing_install passes is_dir() for jdtls_root but (jdtls_root / 'plugins').is_dir() is False during EclipseJDTLS startup in upstream mode.

Common situations: User set jdtls_path to a vscode-java VSIX extension directory (needs '<extension>/server'); extracted the tarball into a nested folder so the real root is one level deeper; pointed at a brew Cellar parent rather than the libexec directory.

Related errors


AI-assisted analysis of oraios/serena@7fcbca7e62 (2026-08-29). Data as JSON: /api/errors/62a38dc950766b67. Report an issue: GitHub.