Java Language Server

repository·master·Indexed 21 days ago

https://github.com/georgewfraser/java-language-server

A minimalist Language Server for Java that leverages the Java compiler API to provide language intelligence features. It supports VS Code (via the vscode-javac extension v0.2.49), Vim (via vim-lsc), Emacs (via Eglot), KDE Kate, and Sublime 3. The server implements a custom LSP via the org.javacs.lsp module to remain a zero-dependency native application and uses file-level incremental compilation and code erasure to maintain high performance.

Tokens
6K
Snippets
18
Records
34
Agent score
74%

What's inside java-language-server

  1. Overview of the org.javacs.lsp module

    master
    The org.javacs.lsp module is a minimalist implementation of the Language Server Protocol (LSP). It is specifically designed to serve the requirements of the Java Language Server (JLS) rather than providing a full, exhaustive implementation of the entire LSP specification. This focus allows for a lightweight footprint tailored to Java development features.
  2. Understand how java-language-server achieves performance via incremental updates

    master

    The language server uses the Java compiler API to provide features like linting and autocomplete. To maintain high performance, it employs two main strategies:

    1. File-level Incremental Compilation: It maintains a long-lived instance of the Java compiler, recompiling only the files that have changed.
    2. Code Erasure (Focusing): When servicing a request (like autocomplete), the server erases irrelevant code within a file to reduce the compilation workload.

    Example: If you are typing print inside a main method, the server erases the contents of other methods (like printFoo() or printBar()) before compiling the context for autocomplete. This dramatically speeds up the response time by focusing only on the region of interest.

  3. Design rationale for the LSP implementation

    master
    The Java Language Server (JLS) uses a custom LSP implementation instead of lsp4j to achieve the goal of being a zero-dependency native application. This design ensures the server works correctly regardless of the system's installed Java version. Specifically, the implementation avoids lsp4j because lsp4j is incompatible with the Java Module System, which is a requirement for using jlink to create a self-contained runtime.
  4. Install java-language-server for KDE Kate

    master

    1. Build the server:

    • Checkout the repository.
    • Run the appropriate download script: ./scripts/download_{linux|mac|windows}.sh.
    • Run the link script: ./scripts/link_{linux|mac|windows}.sh.
    • Build the package: mvn package -DskipTests.

    2. Configure Kate:

    • Go to Settings > Configure Kate... > LSP Client > User Server Settings.
    • Add the following JSON configuration (replacing <path-to-java-language-server> with your actual path):
    {
        "servers":
        {
            "java":
            {
                "command": ["bash","<path-to-java-language-server>/java-language-server/dist/lang_server_{linux|mac|windows}.sh"],
                "url": "https://github.com/georgewfraser/java-language-server",
                "highlightingModeRegex": "^Java$"
            }
        }
    }
  5. Install java-language-server for Emacs (using Eglot)

    master

    1. Build the server:

    • Checkout the repository.
    • Run the appropriate download script: ./scripts/download_{linux|mac|windows}.sh.
    • Run the link script: ./scripts/link_{linux|mac|windows}.sh.
    • Build the package: mvn package -DskipTests.

    2. Configure Emacs:

    • Ensure eglot is installed (built-in for Emacs 29+; otherwise use M-x package-install eglot).
    • Add this to your init file:
      (add-hook 'java-mode-hook #'eglot-ensure)

    For older Eglot versions (<= 1.12.29): You must manually add the server to eglot-server-programs:

    (add-to-list 'eglot-server-programs '(java-mode . ("<path-to-java-language-server-dir>/dist/lang_server_{linux|mac|windows}.sh")))

    For newer Eglot versions (>= 1.19): You can simply place a script named java-language-server in your PATH. For example, on Linux:

    sudo bash -c 'cat << EOF > /usr/local/bin/java-language-server
    #! /bin/sh
    
    <path-to-java-language-server-dir>/dist/lang_server_linux.sh
    EOF'
    
    sudo chmod +x /usr/local/bin/java-language-server
    (add-hook 'java-mode-hook #'eglot-ensure)
  6. Install java-language-server for Vim (using vim-lsc)

    master

    To use the language server in Vim, you must first build it locally and then configure the vim-lsc plugin.

    1. Build the server:

    • Checkout the repository.
    • Run the appropriate download script: ./scripts/download_linux.sh, ./scripts/download_mac.sh, or ./scripts/download_windows.sh.
    • Run the link script: ./scripts/link_linux.sh, ./scripts/link_mac.sh, or ./scripts/link_windows.sh.
    • Build the package: mvn package -DskipTests.

    2. Configure Vim:

    • Install the natebosch/vim-lsc plugin.
    • Add the following to your vimrc (replacing <path-to-java-language-server> with your actual path):

    Note: This tool is incompatible with vim-lsp because it requires LSP v3.0.

    let g:lsc_server_commands = {'java': '<path-to-java-language-server>/java-language-server/dist/lang_server_{linux|mac|windows}.sh'}
  7. Install java-language-server for Sublime 3 (using LSP)

    master

    1. Build the server:

    • Checkout the repository.
    • Run the appropriate download script: ./scripts/download_{linux|mac|windows}.sh.
    • Run the link script: ./scripts/link_{linux|mac|windows}.sh.
    • Build the package: mvn package -DskipTests.

    2. Configure Sublime 3:

    • Install Package Control and the LSP Package.
    • Go to Preferences > Package Settings > LSP > Settings.
    • Add the following configuration (replacing <path-to-java-language-server> with your actual path):
    {
        "clients":
        {
            "jls":
            {
                "enabled": true,
                "command": ["bash", "<path-to-java-language-server>/java-language-server/dist/lang_server_{linux|mac|windows}.sh"],
                "scopes": ["source.java"],
                "syntaxes": ["Packages/Java/Java.sublime-syntax"],
                "languageId": "java"
            }
        }
    }
  8. How Maven dependency resolution works

    master

    When a pom.xml is detected in the workspace root, the language server uses the Maven CLI to resolve dependencies. It executes the following commands:

    • For Classpath: mvn validate dependency:list -DincludeScope=test -DoutputAbsoluteArtifactFilename=true --batch-mode
    • For Source/Javadoc: mvn validate dependency:sources -DincludeScope=test -DoutputAbsoluteArtifactFilename=true --batch-mode

    Note: The server requires the mvn executable to be available on your system's PATH.

  9. How Bazel dependency resolution works

    master

    For Bazel projects (detected via a WORKSPACE file), the language server resolves dependencies by querying the Bazel action graph. It uses:

    • bazel query to find targets of kind java_proto_library.
    • bazel aquery with specific mnemonics (Javac, JavaSourceJar) and filters (--classpath, --sources) to extract absolute paths to artifacts and source jars.

    This allows the language server to support complex Bazel-managed environments, including protocol buffer libraries.

  10. Configure external dependencies for java-language-server

    master

    The language server detects dependencies via pom.xml, Bazel, or manual configuration in .vscode/settings.json.

    Using java.externalDependencies

    You can specify dependencies using Maven or Gradle formats. The server looks for these in your local ~/.m2 or ~/.gradle caches.

    Recommended Setup: Use your build tool to download both the library and the source jars to enable inline Javadoc help:

    • Maven: Run mvn dependency:resolve (for autocomplete) and mvn dependency:resolve -Dclassifier=sources (for Javadoc).
    • Gradle: Include classifier: sources in your build.gradle dependencies.

    Manual Classpath and DocPath

    If automatic detection fails, you can manually specify paths:

    {
        "java.externalDependencies": [
            "junit:junit:jar:4.12:test",
            "junit:junit:4.12"
        ],
        "java.classPath": [
            "lib/some-dependency.jar"
        ],
        "java.docPath": [
            "lib/some-dependency-sources.jar"
        ]
    }
  11. Manage breakpoints in JavaDebugServer

    master

    Breakpoints can be set for specific source files and lines. The server handles two states for breakpoints:

    1. Verified: The class containing the line is already loaded in the VM. The breakpoint is enabled immediately.
    2. Pending: The class has not yet been loaded. The server registers a ClassPrepareRequest for the class name. Once the VM loads the class, the server automatically enables the pending breakpoint.

    Supported Operations:

    • setBreakpoints: Sets breakpoints for a specific source file. Existing breakpoints for that file are disabled before new ones are applied.
    • setFunctionBreakpoints: (Not yet implemented)
    • setExceptionBreakpoints: (Not yet implemented)