eglot-java icon indicating copy to clipboard operation
eglot-java copied to clipboard

Java extension for the eglot LSP client

#+TITLE: README

[[https://github.com/yveszoundi/eglot-java/blob/main/LICENSE][file:http://img.shields.io/badge/license-GNU%20GPLv3-blue.svg]] [[https://melpa.org/#/eglot-java][file:https://melpa.org/packages/eglot-java-badge.svg]] [[https://stable.melpa.org/#/eglot-java][file:https://stable.melpa.org/packages/eglot-java-badge.svg]]

All the bugs reports are welcome and appreciated.

  • Please note that the best way to contribute is via pull requests.
  • I may not notice immediately when something is broken, as I don't really program professionally anymore.
  • Overview

This package provides additional Java programming language support for [[https://github.com/joaotavora/eglot][eglot]].

  • Few convenience functions are available for common operations such as creating a new project or class.
  • There are couple of customization options, including passing custom JVM arguments to the =Java= language server.
  • The [[https://github.com/eclipse/eclipse.jdt.ls][Eclipse JDT language server]] is automatically installed (if needed) when you open a =Java= file:
    • This downloads the latest known milestone release
    • This gives you code completion, refactoring capabilities and other features

|------------------------------------+-----------------------------------------------------------------------------------------+-----------| | Function | Description | Auto-load | |------------------------------------+-----------------------------------------------------------------------------------------+-----------| | =eglot-java-project-new= | Create new projects with a wizard ([[https://start.spring.io/][Spring]], [[https://micronaut.io/][Micronaut]], [[https://quarkus.io/][Quarkus]], [[https://vertx.io/][Vert.x]], [[https://maven.apache.org/][Maven]] or [[https://gradle.org/][Gradle]]) | YES | | =eglot-java-project-build-refresh= | Rebuild the current project | NO | | =eglot-java-project-build-task= | Run a build task using [[https://gradle.org/][Gradle]] or [[https://maven.apache.org/][Maven]] for the current project | NO | | =eglot-java-file-new= | Wizard for creating new Java files | NO | | =eglot-java-run-main= | Run the current main class | NO | | =eglot-java-run-test= | Run the current test ([[https://junit.org/junit5/][JUnit]] only, unless you run Maven or Gradle tasks) | NO | | =eglot-java-upgrade-lsp-server= | Upgrade the LSP server installation | YES | | =eglot-java-upgrade-junit-jar= | Upgrade the JUnit jar installation | YES | |------------------------------------+-----------------------------------------------------------------------------------------+-----------|

Note: eglot-java dynamically modifies the eglot-server-programs variable, you can change that behavior with the variable eglot-java-eglot-server-programs-manual-updates (See https://github.com/yveszoundi/eglot-java/issues/44).

  • You may prefer using the eglot defaults (=jdtls= Python script), =eglot-java= doesn't use that
  • =eglot-java= calls the relevant Java command directly, both for historical reasons and for potentially avoiding any Python dependency (Windows, Mac OS)
  • Dependencies
  • The only direct Emacs package dependency is [[https://github.com/joaotavora/eglot][eglot]], assuming you're running a recent Emacs version.
  • The [[https://projects.eclipse.org/projects/eclipse.jdt.ls/downloads][Eclipse JDT Language server]] along with the [[https://mvnrepository.com/artifact/org.junit.platform/junit-platform-console-standalone][JUnit console runner]] are configured to be automatically installed when not found at default locations (see =M-x customize-group= /eglot-java/).
    • The Eclipse JDT.LS server has [[https://github.com/eclipse-jdtls/eclipse.jdt.ls#features][several features]], and it's recommended that you use a recent enough version of the Java Development Toolkit ([[https://www.oracle.com/java/technologies/downloads/][JDK]]).
    • This emacs package has been tested for few years (roughly since mid-2018).
  • Please also configure your [[https://www.java.com/en/download/help/path.html][PATH]] environment variable [[https://www.tutorialspoint.com/maven/maven_environment_setup.htm][for Maven]] and/or [[https://docs.gradle.org/current/userguide/installation.html][for Gradle]]. =Gradle= support isn't that mature compared to =Maven= overall (Maven has been around much longer...).
  • Installation

The =eglot-java= package is available on [[https://melpa.org/#/getting-started][MELPA]].

Once the =MELPA= repository is configured, please run =M-x package-install= and type =eglot-java=.

  • Usage

** Customization

You can configure few settings to reflect your preferences via =M-x customize-group= (/eglot-java/):

  • You can set the default LSP server installation folder, etc.
  • You can specify =JVM= arguments for the LSP server (=eglot-java-eclipse-jdt-args= variable).
  • You can control eglot initialization of LSP server settings through the =eglot-java-user-init-options= variable. Its value will be merged with defaults (e.g., specifying code formatting profiles, etc.).
  • etc.

** Configuration

*** Initialization

Below is a sample minimal configuration, without any fancy library such as =use-package= or similar.

#+begin_src elisp (add-hook 'java-mode-hook 'eglot-java-mode) (with-eval-after-load 'eglot-java (define-key eglot-java-mode-map (kbd "C-c l n") #'eglot-java-file-new) (define-key eglot-java-mode-map (kbd "C-c l x") #'eglot-java-run-main) (define-key eglot-java-mode-map (kbd "C-c l t") #'eglot-java-run-test) (define-key eglot-java-mode-map (kbd "C-c l N") #'eglot-java-project-new) (define-key eglot-java-mode-map (kbd "C-c l T") #'eglot-java-project-build-task) (define-key eglot-java-mode-map (kbd "C-c l R") #'eglot-java-project-build-refresh)) #+end_src

  • Other notes

** LSP server startup errors

Usually this is due to starting the =LSP= server with an old Java version (see issue [[https://github.com/yveszoundi/eglot-java/issues/29][#29]]).

  • Please ensure that your =JVM= version meets the [[https://github.com/eclipse-jdtls/eclipse.jdt.ls#requirements][compatibility requirements]] for the [[https://projects.eclipse.org/projects/eclipse.jdt.ls][Eclipse JDT.LS server]].
  • As of early 2024, you need to start the =LSP= server with =JDK 17= or later.

** Intermittent eglot timeout errors

You might want to set the value of =eglot-sync-connect= or =eglot-connect-timeout=.

  • Please consult inspect the relevant variable documentation documentation with =C-h v=.
  • See =eglot= issues [[https://github.com/joaotavora/eglot/issues/68][#68]] and [[https://github.com/joaotavora/eglot/issues/1342][#1342]]

** Gradle projects

If you have issues with [[https://gradle.org/][Gradle projects]] (code completion not working), then it's likely due to [[https://docs.gradle.org/current/userguide/compatibility.html][version incompatibilities]] (JDK and bundled Gradle Eclipse versions):

  • The safe approach is to always rely on the Gradle wrapper, accordingly to the [[https://docs.gradle.org/current/userguide/compatibility.html][Gradle compatibility matrix]]
  • As of December 2022, the latest Eclipse JDT.LS would include the [[https://github.com/eclipse/eclipse.jdt.ls/blob/master/org.eclipse.jdt.ls.target/org.eclipse.jdt.ls.tp.target#L14][buildship plugin version 3.17.x]] for Gradle support
  • For version =3.17.x= that [[https://github.com/eclipse/buildship/blob/master/org.gradle.toolingapi/META-INF/MANIFEST.MF][translates into Gradle 7.4.2 or later]]
  • =Gradle 7.4.2= would not be compatible with let's say =JDK 19=, the user would need [[https://docs.gradle.org/current/userguide/compatibility.html][Gradle 7.6]], thus why it's best to always use/generate the Gradle wrapper for peace of mind

** Class file navigation

The =classFileContentsSupport= capability is registered with some known limitations. After visiting an initial "class contents buffer", further type definition navigation is not supported. This can be mitigated by the following workflow:

  • Go back to the previous =Java= buffer
  • Call =M-x xref-find-apropos= with the name of the class to lookup (fully qualified name or simple class name)
    • Sometimes the fully qualified class name gives you good results
    • However, if you don't see the class name in question, please type the simple class name instead

** LSP server upgrades

In earlier versions of =eglot-java=, the LSP server installation was reflecting the latest available snapshot.

As of =eglot-java 1.11= (December 2023), only milestones releases will be installed in order to mitigate challenges with buggy snapshot versions (See issues [[https://github.com/yveszoundi/eglot-java/issues/15][#15]] and [[https://github.com/yveszoundi/eglot-java/issues/16][#16]] for reference).

** LSP server initialization options

Sometimes you may want to add/modify [[https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line#initialize-request][LSP server initialization settings]]. There are tons of them...

  • For basic flexibility, you can control the =settings= node of the LSP server configuration via the variable [[https://www.gnu.org/software/emacs/manual/html_node/eglot/User_002dspecific-configuration.html][eglot-workspace-configuration]]. This is best suited for project-specific configuration.
  • For greater flexibility, you can leverage the =eglot-java-user-init-opts-fn= variable
    • You'll need to bind the value of =eglot-java-user-init-opts-fn= with your own callback function
    • You'll need to return a [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Property-Lists.html][property list]] of valid JDT.LS settings (merged with defaults):
      • In =eglot= keys (property names) are keywords symbols (e.g., =:key=)
      • Instead of curly braces in JSON, you use nested parenthesis (e.g., =(:java (:home "/usr/share/jdk21"))=)
      • For boolean values, use =t= for "true" and =:json-false= for "false"

In the example below, the Google style of formatting is configured for later invocation via =M-x eglot-format=.

#+begin_src emacs-lisp (setq eglot-java-user-init-opts-fn 'custom-eglot-java-init-opts) (defun custom-eglot-java-init-opts (server eglot-java-eclipse-jdt) "Custom options that will be merged with any default settings." '(:settings (:java (:format (:settings (:url "https://raw.githubusercontent.com/google/styleguide/gh-pages/eclipse-java-google-style.xml") :enabled t))))) #+end_src

The list of LSP server settings is available in the [[https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki][Eclipse JDT.LS wiki]] on GitHub:

  • [[https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki/Language-Server-Settings-&-Capabilities][General summary and list of extended capabilities]]
  • [[https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line#initialize-request][Available settings (names and data types)]]

Per general Eclipse JDT.LS documentation, a basic skeleton of an initialization customization could look as follow:

#+begin_src emacs-lisp '(:bundles: ["/home/me/.emacs.d/lsp-bundles/com.microsoft.java.debug.plugin-0.50.0.jar"] :workspaceFolders: ["file:///home/me/Projects/mavenproject"] :settings: (:java (:home "/usr/local/jdk21")) :extendedClientCapabilities (:classFileContentsSupport t)) #+end_src

** Debugging support

Please first setup the LSP =:bundles= in custom LSP initializing settings (per previous example)

  • You can download the latest version of the [[https://github.com/microsoft/java-debug][Microsoft Debug Adapter Protocol (DAP)]] jar from [[https://repo1.maven.org/maven2/com/microsoft/java/com.microsoft.java.debug.plugin/][Maven central]]
  • I then recommend installing [[https://github.com/svaante/dape][dape]]
    • The package is available on [[https://elpa.gnu.org/packages/dape.html][GNU ELPA]] (=M-x package-install=)
    • Utility functions developed by [[https://github.com/MagielBruntink/dape/blob/jdtls-extension/dape-jdtls.el][MagielBruntink]] can be useful

** Systems such as NixOS or Gnu Guix

While =eglot-java= offers few auto-configuration settings for user convenience, this can be challenging for [[https://nixos.org/][NixOS]] or [[https://guix.gnu.org/][Gnu Guix]] users.

There are couple of variables that you can customize (=M-x customize-variable=):

  • You can set a fixed location for the =LSP= installation folder (=eglot-java-server-install-dir=)
  • You can define additional JVM parameters to avoid read-only file system errors (=eglot-java-eclipse-jdt-args=)
  • You can configure the folder containing the server =config.ini= file (=eglot-java-eclipse-jdt-config-directory=)

Please also see [[https://github.com/yveszoundi/eglot-java/issues/46#issuecomment-2016032963][issue #46]].