This is the first part of a few tutorials on Java development with Neovim using the LazyVim setup. I highly recommend LazyVim because it has many cool plugins configured with nice defaults. Moreover, as we see in this tutorial, it lets you quickly have the Java LSP up and running in Neovim.
You need Java installed (possibly 21), while Maven is optional.
I will use this Maven example during the tutorial: https://github.com/LorenzoBettini/maven-bank-example, which is part of my TDD book.
You can follow this step-by-step tutorial by implementing the steps starting from a fresh LazyVim installation (see http://www.lazyvim.org/installation). If you already use LazyVim, you might want to do the steps in this tutorial on a fresh, separate Neovim configuration directory before applying all the configurations in your daily-driver Neovim configuration.
For example, if you start with the starter project provided by LazyVim, you can clone it into a separate configuration directory:
1 |
git clone https://github.com/LazyVim/starter ~/.config/lazyvim-java |
And then you start Neovim pointing to that configuration:
1 |
NVIM_APPNAME=lazyvim-java nvim |
This way, your existing Neovim configuration won’t be touched, and you’ll be free to experiment with this new configuration.
In these tutorials, the path is always intended relative to the Neovim configuration path when referring to a configuration file. If you follow the above commands, for example, it will be relative to “~/.config/lazyvim-java”.
I suggest you try to follow the tutorials by manually executing the configuration steps in such a new separate Neovim configuration.
The final result of this series of tutorials can be found here: https://github.com/LorenzoBettini/lazyvim-java. The “main” branch always points to the latest blog post.
The end of the first part is the branch “first-blog-post”.
A few initial notes
LazyVim comes with autoformatting on save. If you don’t want to disable that in configuration, at least you can use these commands to toggle it during the current session:
- “<leader> u f” Toggle Auto Format (Global)
- “<leader> u F” Toggle Auto Format (Buffer)
Moreover, by default, LazyVim is configured to replace tabs with spaces. I don’t like such a behavior globally, so I disable it by adding this line to the existing file “config/options.lua”
1 |
vim.opt.expandtab = false -- Don't use spaces instead of tabs |
To make the tutorial more readable, I will use a few different “light” themes in Neovim instead of the default dark one. Remember that you can temporarily switch the theme with “leader uC”.
Let’s start
If you cloned the above Maven Java example, you can try opening one of its Java files in Neovim:
Without the Java LSP, you only have basic syntax highlighting but no other IDE tooling.
Let’s install the LazyVim “Java Extra”. A LazyVim extra is meant to configure several plugins and configurations with a single mechanism. In this case, it will configure the Java LSP (“jdtls”, the Java LSP provided by Eclipse, implementing all the excellent Eclipse IDE Java development features) and configure other related plugins and keybindings.
You have some alternatives to enable an “Extra”, which is documented for each available “Extra”; for Java, see http://www.lazyvim.org/extras/lang/java. I prefer to use the UI: Type “:LazyExtras” to show the LazyVim Extras UI:
The LazyExtras UI proposes some recommended extras based on the files you’ve just opened, so it should recommend “lang.java”; if not, search for “lang.java” and when you’re on that line, press “x” to install the extra.
You get the feedback about restarting Neovim:
Specifying an extra as we’ve just done creates a file in the configuration, “lazyvim.json”, with this content (the numbers for news and version might be different depending on when you follow this tutorial):
1 2 3 4 5 6 7 8 9 |
{ "extras": [ "lazyvim.plugins.extras.lang.java" ], "news": { "NEWS.md": "7429" }, "version": 7 } |
The idea is to keep this file in the Git repository for your configuration as part of your dotfiles for Neovim.
You can now try and open a Java file from this project.
It will not work the first time because Mason has to install the LSP. So, it would try to start the Java LSP (“jdtls”) before it is fully installed. After you see that Mason has successfully installed “jdtls”, you can restart Neovim.
Of course, you could force the installation of “jdtls” by using the Mason UI, “:Mason”: select “jdtls” and install it.
Alternatively, if you want a fully automated solution that, besides installing all the Neovim plugins, also automatically installs the Java LSP, you can add this Lua specification in the Neovim configuration folder (the name does not matter; I call it “extend-lsp” to stress that it extends the Mason configuration of LazyVim, https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/plugins/lsp/init.lua) “lua/plugins/extend-lsp.lua” with this content:
1 2 3 4 5 6 7 8 |
return { "williamboman/mason.nvim", opts = { ensure_installed = { "jdtls", }, }, } |
Now, when you start Neovim, “jdtls” will automatically install. Remember that “jdtls” is a big Java JAR, so you might want to check whether Mason has successfully downloaded and installed it or is still installing it through the Mason UI.
It works best if you start Neovim from the root directory of the Maven project (the one containing the “pom.xml”). If you open a Java file from another directory, the configured LSP will still search for a “pom.xml” in the parent directories until it finds one (other valid files are the Gradle and Ant specification files if the project is not based on Maven); the root of a Git repository is also a valid root directory.
The highlighting looks much better, with many more highlighted parts (thanks to Treesitter, which the LazyVim Java Extra includes), and you can see the messages from the Java LSP doing its checks and tasks (including downloading Maven dependencies if they are not already cached):
This is a Maven project, so the Java LSP has to resolve the Maven dependencies. If this is the first time you open this project, you should see messages about downloading Maven dependencies.
When all dependencies have been resolved, and the Java LSP has correctly compiled the sources, you can also see some editor decorations (inlay hints), like the parameter names, when calling a method (of course, those parts are NOT part of the source, but only visual content in the editor):
You can enable/disable inlay hints with “<leader> u h”.
With “<leader> c C” you can also refresh “code lens”; for Java, you get the references to classes and methods:
Hovering support (“K”) allows you to see the Javadoc of the current element, e.g., for String:
Pressing any key will make the pop-up window go away. If you want to scroll the pop-up window, use “Ctrl w w” (switch window), and you can scroll the Javadoc.
You’re ready to develop your Java program using typical IDE tooling, like content assist.
Exiting the “insert mode” automatically triggers Java validation, possibly showing warnings and error markers in the editor.
NOTE: The Jdtls LSP (i.e., how LazyVim configures such an LSP by default) creates an Eclipse workspace in the directory “~/.cache/lazyvim-java/jdtls/” in a subdirectory named after the Maven project, e.g., in this example, “~/.cache/lazyvim-java/jdtls/maven-bank-example”. This is just information since you don’t need to care about the created workspaces, which are meant to be ephemeral.
The example used in this post is an Eclipse project. The LSP detects that and possibly updates the Eclipse metadata files accordingly, e.g., “.classpath”, “.settings” directory, “.project”, etc.). If you start with a Maven project (not Eclipse) since the Java LSP is the one from Eclipse, it will create the above Eclipse metadata files automatically for you.
Interestingly, you could use a plain existing Eclipse project, and the Java LSP would still work in Neovim. For example, this is an Eclipse project (not Maven), again part of my TDD book: https://github.com/LorenzoBettini/assertj-log4j-bank-example. It contains the JARs for Assertj and Log4J, and the “.classpath” refers to the JARs that are included. It works seamlessly in Neovim!
Everything works also with multi-module Maven projects; try, e.g., https://github.com/LorenzoBettini/maven-bank-multimodule-example, again part of my TDD book.
You can explore such IDE tooling by modifying one of the existing files.
Otherwise, stay tuned for future blog posts, where we’ll cover:
- IDE mechanisms provided by this configuration;
- dealing with Maven dependencies (with the current configuration, editing POM files is not yet ideal);
- how to run/debug Java programs (with the current configuration, this is not yet possible);
- how to run tests
- maybe other features
That’s all for the first part; stay tuned! 🙂