I got recently switched to working on a Java codebase. This is a tutorial on how to setup neovim as a proper Java IDE, including a lombok support.

Introduction

After working with computers for almost two decades, vim’s modal editing is wired into my brain and a muscle memory. I do not consider myself as an advanced user, but yanking/pasting, %, splits, textobjects, macros and a few movements that I use are enough for me to stick with it. Not being forced to use mouse is a nice bonus too.

Enter neovim. As a fork of vim, it inherits all good parts and adds dozens of advantages making it a plausible IDE. Among other is brings tree-sitter, native LSP client and Lua as a scripting language.

Unlike int my favorite language Go, where vim/neovim sits proudly in a 3rd place with 19% of respondents claiming use is. The situation in Java world is most likely different. I could not find any specific numbers for it. However, since its inception, the Java world has been dominated by IDEs. Starting with Netbeans - which to my surprise is still an active project under Apache Foundation - through Eclipse IDE, and, most recently Intellij, probably the most popular IDE today. Then there are Spring Tools Suite, JBoss Developer studio as well as a Visual Studio Code. Java has never been short of IDEs.

Neovim plugin(s)

When I’m checking out anew programming language, I check out the nvim-lspconfig first. However, in the case of Java, this is the wrong place to look.

mfussenegger/nvim-jdtls is the plugin I used. It integrates neovim with Eclipse JDT LS. Yes, that is is the same Eclipse, that produces the IDE mentioned earlier. There is also nvim-java/nvim-java too, which I haven’t tested. The nvim-java project itself says

nvim-jdtls is a plugin that follows “Keep it simple, stupid!” approach. If you love customizing things by yourself, then give nvim-jdtls a try.

Which, to be honest, haven’t shed any lights whenever I want to use this or that plugin.

Installation

The installation string is a bit longer, because project is hosted on codeberg.

{
  "mfussenegger/nvim-jdtls",
  url = "https://codeberg.org/mfussenegger/nvim-jdtls",
  ft = "java",
}

Then configure it and enable.

vim.lsp.config("jdtls", {capabilities = capabilities,})
vim.lsp.enable("jdtls")

And that’s all folks. Or isn’t?

Project Lombok

A typical Java class has a few things: the private attributes, constructor and the infamous getter and setter methods. Unlike in Go, making the class attributes public is considered a bad style. However as both languages emphasizes interfaces, you can’t place an attribute to interface defintion - ignoring final static constants in Java. So having getter setters makes a sense.

So here is the typical Java code

public class Foo {
  private int bar;

  public Foo(int bar) {
    this.bar = bar;
  }

  public int getBar() {
    return bar;
  }

  public void setBar(int bar) {
    this.bar = bar;
  }
}

The getter/setter code is very repetitive. The original Java solution was to use IDE to generate the getter and setter code. Project lombok automates the whole thing via plugging into JVM and via special feature called annotation it generates needed methods in a background.

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Foo {
  private int bar;
}

It turns out the nvim-jdtls does not support this out of the box, so LSP compaints all the time about missing methods.

The HOWTO

The TL;DR answer is to check jdtls.lua in my dotfiles. It contains complete setup for it.

I do not want to use Mason, which can automate a lot of LSP related downloads. So the process is a bit manual.

  1. download the lombok.jar and place it somewhere
  2. define lombok_path variable pointing to the lombok.jar
  3. pass it as "-javaagent:" .. lombok_path to the configuration.

And that’s it.

I used the Lua variable lombok_path to debug issues I was facing while configuring neovim. Using the variable made it easier to write a proper notification. So now, when I open a Java project, the confirmation about properly found lombok.jar properly appears at the top of the messages.

local notify = vim.notify
local ok, fidget = pcall(require, "fidget")
if ok then
  notify = fidget.notify
end

-- Verify lombok exists
if vim.fn.filereadable(lombok_path) == 0 then
  notify("Lombok jar not found at: " .. lombok_path, vim.log.levels.ERROR)
else
  notify("Lombok jar found at: " .. lombok_path, vim.log.levels.INFO)
end

Acknowledgments

The following images has been hurt during the logo creation process. And ChatGPT has been used to sketch the original idea.