Neovim + Java LSP on a Play Framework sbt Project — The Missing Guide

Published: (March 26, 2026 at 01:32 PM EDT)
3 min read
Source: Dev.to

Source: Dev.to

The short answer

sbt-eclipse generates Eclipse project files, which JDTLS (Eclipse’s Java language server) consumes. That’s the bridge; everything else is configuration details.

If you’re here, you’ve probably already tried Metals, hit a wall, and Googled your way to disappointment. Here’s the setup that actually works.

Project context

  • Play Framework 3.x, Java (not Scala)
  • sbt with 7 sub‑modules, ~3 000 source files
  • Heavy code generation (OpenAPI, Avro, WSDL)
  • Team uses IntelliJ; you use Neovim

Metals understands sbt natively, but its Java support is minimal (no completions, hover, or organize‑imports). A Metals maintainer even said:

“I can’t imagine the case when somebody wants to use Metals on a full‑java project.”

JDTLS has full Java support, but only understands Maven, Gradle, and Eclipse project formats—it doesn’t know about build.sbt. The solution is to make sbt produce the files JDTLS expects: .classpath and .project.

Three things I learned the hard way

GotchaWhy
Don’t use ThisBuild / scopesbt‑eclipse reads keys at the project level, not ThisBuild. Settings placed under ThisBuild are silently ignored.
ManagedSrc is criticalWithout it, generated sources (Play routes, OpenAPI codegen, Avro) don’t appear in .classpath. ManagedClasses and ManagedResources alone aren’t enough.
preTasks := Seq(Compile / compile)Code generation must run before Eclipse files are created; otherwise the managed source directories don’t exist on disk yet.

sbt‑eclipse configuration

Add the plugin globally

// ~/.sbt/1.0/plugins/plugins.sbt
addSbtPlugin("com.github.sbt" % "sbt-eclipse" % "6.2.0")

Configure the plugin

// ~/.sbt/1.0/global.sbt
import com.typesafe.sbteclipse.core.EclipsePlugin.EclipseKeys

EclipseKeys.projectFlavor := EclipseProjectFlavor.Java
EclipseKeys.skipParents := false
EclipseKeys.withSource := true
EclipseKeys.preTasks := Seq(Compile / compile)
EclipseKeys.createSrc := EclipseCreateSrc.ValueSet(
  EclipseCreateSrc.Unmanaged,
  EclipseCreateSrc.Source,
  EclipseCreateSrc.Resource,
  EclipseCreateSrc.ManagedSrc,
  EclipseCreateSrc.ManagedClasses,
  EclipseCreateSrc.ManagedResources
)

Generate the Eclipse files

sbt eclipse

This command first compiles the project (triggering code generation), then creates .classpath and .project in the root and every submodule.

Add the generated files to .gitignore

.classpath
.project
.settings/

Installing JDTLS via Mason

:MasonInstall jdtls

Root detection configuration for Neovim

In a multi‑module sbt project each submodule has its own build.sbt and .project. If any of those appear in root_markers, JDTLS will treat the submodule as the workspace root, breaking cross‑module imports. Use .git as the primary anchor:

vim.lsp.config("jdtls", {
  root_markers = {
    { "mvnw", "gradlew", "settings.gradle", "settings.gradle.kts", ".git" },
    { "pom.xml", "build.gradle", "build.gradle.kts", "build.xml" },
  },
})
vim.lsp.enable("jdtls")

Note: Nested arrays require Neovim 0.11.3+. The first matching group wins.

If you change the root detection or modules aren’t being discovered, clear JDTLS’s cache:

rm -rf ~/.cache/nvim/jdtls/workspace/

Then restart Neovim; JDTLS will re‑import everything from scratch.

Full Java IDE features in Neovim

  • Code completion
  • Go‑to‑definition (across modules)
  • Find references
  • Diagnostics
  • Organize imports
  • Rename refactoring

Running/debugging Play is still done via sbt (sbt run) in a terminal, because Play’s dev mode doesn’t use a standard main method.

Classpath updates – After changing dependencies in build.sbt, re‑run sbt eclipse and restart JDTLS. In practice this happens only a few times a month. Normal coding, generated‑code changes, and branch switches (with the same dependencies) require no extra action.

Sources and tools

  • sbt‑eclipse – the bridge that makes this possible
  • nvim‑lspconfig – JDTLS configuration for Neovim
  • Mason.nvim – LSP server installer
  • nvim‑metals#503 – discussion confirming Metals won’t work for this use‑case
  • metals#1815 – Metals team’s stance on Java‑only project support
0 views
Back to Blog

Related posts

Read more »

Neovim 0.12.0

text NVIM v0.12.0 Build type: Release LuaJIT 2.1.1774638290 Release notes - Changeloghttps://github.com/neovim/neovim/commit/fc7e5cf6c93fef08effc183087a2c8cc9bf...

Introduction to Java Exception Handling

What is Exception Handling? Exception handling in Java is a mechanism to handle runtime errors so that the normal flow of the program can be maintained. An exc...

#22 Known is a Drop! for loop in java

Definition A control statement used to repeat a block of code multiple times, typically when the number of iterations is known. Syntax java for initializer; co...