README.md

April 19, 2026 · View on GitHub

neotest-java

Neotest adapter for Java, using JUnit.

LuaRocks GitHub Repo stars

image

⭐ Features

  • Maven & Gradle - Full support for both build systems (Groovy & Kotlin DSL)
  • Multi-module projects - Automatic detection and per-module test execution
  • JUnit 5 (Jupiter) - Support for @Test, @ParameterizedTest, @TestFactory, nested tests
  • JUnit Platform 1.10.x & 6.x - Compatible with both legacy and latest versions
  • Spring Framework - Auto-loads application.yml, application-test.yml, and .properties files
  • Debugging with nvim-dap - Full integration with breakpoints, JDWP, and DAP REPL output
  • Incremental compilation - Smart compilation of only changed files via nvim-jdtls
  • Automatic classpath management - Retrieves runtime and test classpaths from LSP
  • JUnit JAR management - Automatic installation, version detection, and upgrade prompts
  • Health check - Comprehensive diagnostics with :checkhealth neotest-java

Check ROADMAP.md to see what's coming!

📦 Installation

Prerequisites

  • Neovim 0.10.4+
  • nvim-treesitter with Java parser: :TSInstall java
  • A JDTLS-based Java LSP — either nvim-jdtls or nvim-java (both are compatible)
  • nvim-dap - For debugging support (optional)

Setup with lazy.nvim

Using nvim-jdtls

return {
  {
    "rcasia/neotest-java",
    ft = "java",
    dependencies = {
      "mfussenegger/nvim-jdtls",
      "mfussenegger/nvim-dap", -- for debugging (optional)
      "rcarriga/nvim-dap-ui", -- recommended
      "theHamsta/nvim-dap-virtual-text", -- recommended
    },
  },
  {
    "nvim-neotest/neotest",
    dependencies = {
      "nvim-neotest/nvim-nio",
      "nvim-lua/plenary.nvim",
      "nvim-treesitter/nvim-treesitter",
    },
    config = function()
      require("neotest").setup({
        adapters = {
          require("neotest-java")({
            -- Optional configuration here
          }),
        },
      })
    end,
  },
}

Using nvim-java

nvim-java is fully compatible — neotest-java communicates with the LSP through the standard vim.lsp.Client API and does not depend directly on nvim-jdtls.

return {
  {
    "rcasia/neotest-java",
    ft = "java",
    dependencies = {
      "mfussenegger/nvim-dap", -- for debugging (optional)
    },
  },
  -- nvim-java handles JDTLS setup separately
  { "nvim-java/nvim-java" },
  {
    "nvim-neotest/neotest",
    dependencies = {
      "nvim-neotest/nvim-nio",
      "nvim-lua/plenary.nvim",
      "nvim-treesitter/nvim-treesitter",
    },
    config = function()
      require("neotest").setup({
        adapters = {
          require("neotest-java")({
            -- Optional configuration here
          }),
        },
      })
    end,
  },
}

JUnit JAR Installation

After setting up the plugin, run:

:NeotestJava setup

This will automatically download and verify the JUnit Platform Console Standalone JAR from Maven Central with SHA-256 checksum verification.

Tip

The plugin will detect if you have an older JUnit version installed and prompt you to upgrade to the latest version.

⚙️ Configuration

All configuration options are optional. Pass them to require("neotest-java")({}):

require("neotest").setup({
  adapters = {
    require("neotest-java")({
      junit_jar = nil, -- default: auto-detected
      jvm_args = { "-Xmx512m" }, -- custom JVM arguments
      incremental_build = true, -- recompile only changed files
      disable_update_notifications = false, -- show JUnit update prompts
      test_classname_patterns = { "^.*Tests?$", "^.*IT$", "^.*Spec$" },
    }),
  },
})

Options

OptionTypeDefaultDescription
junit_jarstring?stdpath("data")/neotest-java/junit-*.jarPath to JUnit Platform Console Standalone JAR
jvm_argsstring[]{}Additional JVM arguments for test execution
incremental_buildbooleantrueEnable incremental compilation (recompile only changed files)
disable_update_notificationsbooleanfalseDisable notifications about available JUnit updates
test_classname_patternsstring[]{"^.*Tests?$", "^.*IT$", "^.*Spec$"}Regex patterns for test class names (classes must match at least one pattern)

⚠️ Troubleshooting

Multi-module projects: "Given URI does not belong to any Java project" (-32001)

Tests in one module pass but tests in another module fail with an error like:

Error -32001: Given URI does not belong to any Java project.

Cause: eclipse.jdt.ls (jdtls) is started once per module instead of once per workspace. When neotest-java runs tests for module B, it ends up talking to module A's jdtls instance, which rejects URIs it doesn't own.

This happens when pom.xml or build.gradle is used as a root marker in the jdtls configuration. Because every module directory contains its own build file, jdtls resolves root_dir to the nearest module root rather than the repository root, and a separate server process is started for each module you open.

Solution: Remove pom.xml and build.gradle from the root_dir markers and keep only the repo-level markers (.git, mvnw, gradlew). These files only exist at the workspace root, so jdtls resolves a single root_dir for the entire monorepo.

With nvim-jdtls (ftplugin/java.lua style):

-- Before (broken for multimodule):
root_dir = require("jdtls.setup").find_root({ ".git", "mvnw", "gradlew", "pom.xml" })

-- After (correct):
root_dir = require("jdtls.setup").find_root({ ".git", "mvnw", "gradlew" })

With the newer vim.lsp.config / vim.fs.root API (Neovim 0.11+):

-- Before (broken for multimodule):
root_dir = vim.fs.root(0, { ".git", "mvnw", "gradlew", "pom.xml" })

-- After (correct):
root_dir = vim.fs.root(0, { ".git", "mvnw", "gradlew" })

With this change a single jdtls instance starts at the repository root and handles all modules. eclipse.jdt.ls natively understands Maven and Gradle multimodule projects, so no further configuration is needed.

Note

The first time you open a Java file after this change, jdtls will reindex the whole workspace from a new -data directory. This can take a couple of minutes for large projects.

Spring Tests Failing with "parameter name information not available"

If you're running Spring tests that use reflection (e.g., @MockBean, @WebMvcTest) and encounter errors like:

java.lang.IllegalArgumentException: Name for argument of type [int] not specified,
and parameter name information not available via reflection.
Ensure that the compiler uses the '-parameters' flag.

Solution: Configure the JDTLS compiler to preserve parameter names in bytecode by adding the following to your project's .settings/org.eclipse.jdt.core.prefs file:

org.eclipse.jdt.core.compiler.codegen.methodParameters=generate

If the .settings directory doesn't exist, create it in your project root:

mkdir -p .settings
echo "org.eclipse.jdt.core.compiler.codegen.methodParameters=generate" > .settings/org.eclipse.jdt.core.prefs

After adding this setting, restart your LSP server (:LspRestart) and run your tests again.

🤝 Contributing

Contributions are welcome! Please feel free to:

  • 🐛 Report bugs and issues
  • 💡 Suggest new features or improvements
  • 🔧 Submit pull requests

See CONTRIBUTING.md for guidelines.

Running Tests

The project includes both unit tests and end-to-end (E2E) tests:

# Run unit tests
make test

# Run E2E tests (requires Java and JAVA_HOME)
make test-e2e

# Run all tests
make test && make test-e2e

E2E Test Requirements:

  • Java JDK (11 or higher)
  • JAVA_HOME environment variable set
  • Maven wrapper is included in the test fixture

See tests/e2e/README.md for detailed E2E test documentation.

✨ Acknowledgements

Thanks to all contributors who have helped improve this project!

Contributors