☕ jc.nvim
July 28, 2026 · View on GitHub
☕ jc.nvim
A Java productivity layer for Neovim, on top of an externally managed jdtls.
Code generation · class creation · test runner · build runner · refactorings · debugging
jc.nvim never starts or installs the language server. You run jdtls with
nvim-java,
nvim-jdtls or nvim-lspconfig, and
jc.nvim hooks into whatever jdtls client attaches and adds the ergonomics of
vim-javacomplete2 (its
predecessor), rebuilt on Neovim's built-in LSP client.

Table of contents
✨ Features
- 🛠️ Code generation —
toString,hashCode/equals, constructors, accessors, all with interactive field selection; add unimplemented (abstract) methods. - 📦 Organize imports — a smart mode that remembers your preferred class per
ambiguous name, per project; remove-unused / add-missing without reordering
the rest; replace an import by picking among same-named types; add an
annotation to a method or class by a name prefix (
Get→Getter/GetMapping/…, picks and imports the chosen type) — a live telescope picker when available, otherwise a prompt +vim.ui.select. - 🏗️ Class creation — a one-line DSL (or a step-by-step wizard) with
<Tab>completion, project-aware package/module resolution and a library of templates (records, spring stereotypes, JPA entity, JUnit, …). - 🧪 Test runner — run JUnit tests through neotest with the classpath resolved from jdtls (debug via nvim-jdtls/nvim-java); optional.
- ⚙️ Build runner — run gradle/maven tasks with a module + task picker; compile errors go to the quickfix list.
- 🔧 Refactorings — extract variable / method, convert to static import,
flip the receiver and argument of a call (
a.equals(b)→b.equals(a)), move a class to another package (references updated). - 🧷 Snippets — an optional VS Code snippet set (field/modifier combos, NetBeans-style abbreviations) for your snippet engine.
- 🐞 Debugging — attach/launch via nvim-dap or vimspector, with per-project host/port memory.
- 🧭 Navigation — jump between a class and its test; go to a file by its
fully-qualified name (an FQN-aware
gf). - 🧰 Utilities — classpath-aware
javap/jshell/jol; decompiledjdt://class view; wipe a corrupted jdtls workspace.
Generating a constructor and toString — the picker windows let you pick the
fields and the style:

Adding an annotation by name — a live search over jdtls' types inserts the
@Annotation and its import:

Replacing an import — pick a different same-named type and the import line is swapped in place:

📋 Requirements
- Neovim ≥ 0.10 (0.11+ recommended).
- A running
jdtlsfrom nvim-java, nvim-jdtls or lspconfig, started withextendedClientCapabilities(notablyexecuteClientCommandSupportandadvancedOrganizeImportsSupport) — nvim-java and nvim-jdtls do this out of the box. - Optional, per feature:
- debug — the java-debug
bundle in jdtls (nvim-java bundles it; nvim-jdtls: add it to
init_options.bundles) and nvim-dap or vimspector. - test runner — neotest (see Test runner).
:JCutilJoldownloads the jol-cli jar into~/.m2on first use.
- debug — the java-debug
bundle in jdtls (nvim-java bundles it; nvim-jdtls: add it to
📦 Installation
lazy.nvim, jdtls managed by nvim-java (recommended)
return {
"artur-shaik/jc.nvim",
ft = { "java" },
dependencies = { "nvim-java/nvim-java" },
opts = {
keys_prefix = "<leader>j",
},
}
With the optional test runner (neotest)
{
"artur-shaik/jc.nvim",
ft = { "java" },
dependencies = {
"nvim-java/nvim-java",
{
"nvim-neotest/neotest",
optional = true,
dependencies = { "nvim-neotest/nvim-nio", "nvim-lua/plenary.nvim" },
opts = function(_, opts)
opts.adapters = opts.adapters or {}
table.insert(opts.adapters, require("jc").neotest_adapter())
-- optional: auto-close the summary on an all-green focused run
opts.consumers = opts.consumers or {}
opts.consumers.jc = require("jc").neotest_consumer()
end,
},
},
opts = { keys_prefix = "<leader>j" },
}
jc.nvim works with any owner of the jdtls client — nvim-jdtls or a plain
lspconfig setup are fine too; just drop nvim-java from dependencies and
start jdtls your own way.
If setup is never called, opening a java file initializes the plugin with
defaults.
⚙️ Configuration
All options go through setup(opts) (or your plugin manager's opts):
require("jc").setup({
keys_prefix = "<leader>j", -- prefix for the default mappings
default_mappings = true, -- install default mappings on attach
autoformat_on_save = false, -- format java buffers on save
debug_backend = nil, -- "dap" | "vimspector" | nil (auto-detect)
basedir = nil, -- data dir, default ~/.local/share/jc.nvim
update_config_on_new_file = true, -- refresh jdtls build path on new java files
templates_dir = nil, -- dir of user class templates
class_type_exclude = nil, -- package prefixes hidden from type completion
class_prompt = "oneline", -- "oneline" (DSL) | "wizard" (step-by-step)
map_gf = true, -- override gf with an FQN-aware go-to-file
on_attach = nil, -- function(client, bufnr) extra hook
test = { -- test runner (see Test runner)
precompile = false, -- compile with gradle/maven before a run
notify = true, -- toast run start / result
open_summary = true, -- open the neotest summary on a run
autoclose_summary = true, -- close it after an all-green focused run
console_launcher_path = nil, -- path to the JUnit console-standalone jar
debug = nil, -- test debugger: nil = jc-native, "external" = nvim-jdtls/nvim-java
},
})
class_prompt = "wizard"
Swaps the one-line DSL prompt for a step-by-step vim.ui.select/vim.ui.input
flow (template → module → package → name → extends/implements/fields/flags).
Each step is a short clean list, which avoids the cmdline-completion truncation
of very long package paths. The mapping <p>N always runs the wizard,
regardless of this option.
class_type_exclude
Adds package prefixes to hide from the extends/implements/field-type
completion. The prompt resolves types from jdtls' workspace symbols, which
include non-importable ones; nested classes, shaded jars, internal/impl
packages and a built-in list of known JDK/library internals (sun.*,
com.sun.*, jdk.internal, jackson introspect/cfg/…) are dropped
automatically. The LSP gives no visibility, so package-private classes in
ordinary packages can still slip through — add their prefixes here, e.g.
{ "com.example.somelib.internalish" }.
update_config_on_new_file
A java file created in-editor isn't on jdtls' build path until the project
configuration is refreshed, so go-to-definition returns nothing on it (while
find-references still works off the search index). With this on (default), jc
detects such files and fires :JCutilUpdateConfig on their first write. Set it
to false to refresh manually.
Legacy globals
g:jc_default_mappings, g:jc_autoformat_on_save, g:jc_debug_backend and
g:jc_basedir still work as a fallback when the corresponding option isn't
passed to setup.
📜 Commands
:checkhealth jc verifies the setup; :help jc has the full reference.
Imports & code generation
| Command | Action |
|---|---|
JCimportsOrganizeSmart | organize imports, auto-picking remembered classes |
JCimportsOrganize | organize imports, choosing from the candidate list |
JCimportsReplace | replace the import of the type under the cursor (pick among same-named, e.g. lombok.Value vs spring's) |
JCimportsRemoveUnused | remove all unused imports (no reordering) |
JCimportsAddMissing | add all missing imports (no reordering; smart-picks ambiguous names) |
JCimportsOrganizeNoSort | add missing + remove unused, without reordering the rest |
JCgenerateToString | generate toString() with field selection |
JCgenerateHashCodeAndEquals | generate hashCode() and equals() |
JCgenerateAccessors | choose fields for accessor generation |
JCgenerateAccessorGetter / …Setter / …SetterGetter | getter / setter / both for a field |
JCgenerateConstructor | choose fields for a constructor |
JCgenerateConstructorDefault | no-arg constructor |
JCgenerateAbstractMethods | add unimplemented methods |
Class creation & navigation
| Command | Action |
|---|---|
JCgenerateClass | class creation prompt (DSL or wizard per class_prompt) |
JCgenerateClassWizard | class creation, always the step-by-step wizard |
JCgenerateClassFromCursor | create the class named under the cursor (pick package/module, then the DSL) |
JCgotoTest | jump to the test class (or back), creating it if missing |
JCgotoFqn | open the java file for the FQN under the cursor |
Refactor
| Command | Action |
|---|---|
JCrefactorExtractVar | extract variable (all occurrences) |
JCrefactorExtractMethod | extract method (visual range) |
JCrefactorStaticImport | convert the call at the cursor to a static import |
JCrefactorStaticImportEnum | static-import every constant of the enum |
JCrefactorFlipArgs | swap receiver and argument of the call at the cursor (a.equals(b) → b.equals(a)) |
JCrefactorMove | move the current class to another package/source root, updating references |
JCannotateMethod / JCannotateClass | add an annotation to the enclosing method / class (search jdtls by name, import remembered) |
Test runner
| Command | Action |
|---|---|
JCtestRun | run the test at the cursor |
JCtestFile | run every test in the current file |
JCtestDebug | debug the test at the cursor (delegates to nvim-jdtls / nvim-java) |
JCtestSuite | run every test under the project root |
JCtestPick | pick a test class from the whole project and run it |
JCtestLast | re-run the last test position |
JCtestStop | stop the running test |
JCtestSummary / JCtestOutput | toggle summary / open the test's output |
JCtestPrecompile | toggle build-tool precompile before a run |
JCtestInstall | download the JUnit console launcher via maven |
Build runner
| Command | Action |
|---|---|
JCbuildRun [args] | run gradle/maven with args (or prompt, defaulting to the last run) |
JCbuildTask | pick a module then a task/goal |
JCbuildLast | repeat the last build task |
Debug & utilities
| Command | Action |
|---|---|
JCdebugAttach / JCdebugLaunch | attach / launch the debugger |
JCdapAttach / JCvimspectorAttach | attach with a specific backend |
JCdebugWithConfig | start with a chosen vimspector configuration |
JCtoggleAutoformat | toggle format-on-save |
JCutilUpdateConfig | re-read the project configuration (pom/gradle) |
JCutilWipeWorkspace | delete the jdtls workspace and restart (works even if jdtls failed to start) |
JCutilJshell | java shell with the project classpath |
JCutilBytecode | bytecode of the current class (javap) |
JCutilJol | object layout (jol) |
⌨️ Mappings
Installed on jdtls attach when default_mappings is enabled. <p> is
keys_prefix (default <leader>j).
| Mode | Keys | Action |
|---|---|---|
| n | <p>i / <p>I | organize imports — smart (sorted) / no reordering |
| i | <C-j>i | organize imports |
| n | <p>ts | toString() |
| n | <p>eq | hashCode() and equals() |
| n | <p>A | accessors (field selection) |
| n | <p>s / <p>g / <leader>ja | setter / getter / both |
| i | <C-j>s / <C-j>g / <C-j>a | accessor generation |
| n | <p>c / <p>cc | constructor (fields) / default constructor |
| n | <p>m, i <C-j>m | abstract methods |
| n | <p>n / <p>N | new class — prompt / wizard |
| n | <p>nc | create the class named under the cursor (missing from the project) |
| n | <p>t | jump to the test class (or back) |
| n | gf | go to file, or the java file of the FQN under the cursor |
| n | <p>Tr / <p>Tf / <p>Ta / <p>Tl | run test at cursor / file / all / last |
| n | <p>Td | debug the test at the cursor (via nvim-jdtls / nvim-java) |
| n | <p>Tp | pick a test class from the project and run it |
| n | <p>Ts / <p>To | toggle test summary / open test output |
| n | <p>b / <p>B | run gradle/maven (prompt) / pick a task |
| n | <p>da / <p>dl | debug attach / launch |
| v | <p>re / <p>rm | extract variable / method (selection) |
| n | <p>re | extract variable, all occurrences (at cursor) |
| n | <p>rs / <p>rS | static import — call / every enum constant |
| n | <p>rp | replace the import of the type under the cursor |
| n | <p>ru / <p>ri | remove unused / add missing imports (no reordering) |
| n | <p>rf | flip receiver and argument of the call (a.equals(b) → b.equals(a)) |
| n | <p>rM | move the current class to another package (references updated) |
| n | <p>am / <p>ac | add an annotation to the enclosing method / class |
🏗️ Class creation
:JCgenerateClass (<p>n) opens a one-line prompt. The scheme, slot by slot:
template : [subdir] : /package.ClassName extends X implements Y (fields) :flags
└── 1 ─┘ └── 2 ─┘ └─────── 3 ──────┘ └──────── 4 ─────────┘ └── 5 ─┘ └ 6 ┘
| # | Slot | Meaning |
|---|---|---|
| 1 | template: | (optional) a template — record, entity, service, junit5, … (see Templates) |
| 2 | [subdir]: | (optional) a source-set or subproject (see below) |
| 3 | /package.Name | class name and package. Leading / = absolute in the source root; without it, relative to the current file's package |
| 4 | extends/implements | (optional) supertypes, imported automatically |
| 5 | (fields) | (optional) type name, comma-separated, private by default. For enum this slot lists the constants |
| 6 | :flags | (optional) code-gen and lombok flags (see below) |
Everything except the class name is optional — /com.app.User alone makes an
empty class.
Examples
Absolute (leading /) — the package is taken literally:
| Prompt | Creates |
|---|---|
/com.app.User(String name, int age) | a User class with two fields |
/com.app.User(String name):constructor:toString | …plus an all-args constructor and toString |
record:/com.app.Point(int x, int y) | a record Point(int x, int y) |
entity:/com.app.Order(String number) | an @Entity with an @Id id and @Column fields |
interface:/com.app.OrderRepo extends CrudRepository | an interface extending CrudRepository |
enum:/com.app.Status(NEW, PAID, SHIPPED) | an enum with those constants |
service:/com.app.OrderService | an @Service class |
/com.app.UserDto(String id, String name):lombokData | a class annotated @Data |
[test]:/com.app.UserTest | a class under src/test/java |
[core]:/com.app.Foo | a class in the core module (multi-module) |
Relative (no leading /) — the package is resolved against the current
file's package. Editing com.app.service.OrderService:
| Prompt | Creates |
|---|---|
Helper | com.app.service.Helper |
Helper(String name) | …with a field |
util.Strings | com.app.service.util.Strings (a sub-package) |
record:Money(long amount) | com.app.service.Money from the record template |
Flags
Trailing :flag segments run after the class is created. Code generation
flags go through jdtls:
| Flag | Generates |
|---|---|
constructor | an all-fields constructor |
toString | toString() |
hashCode | hashCode() |
equals | equals() |
Lombok flags add the annotation (and its import, resolved by organize-imports) instead of generating code:
| Flag | Annotation | Flag | Annotation | |
|---|---|---|---|---|
lombok / lombokData | @Data | lombokNoArgs | @NoArgsConstructor | |
lombokValue | @Value | lombokAllArgs | @AllArgsConstructor | |
lombokBuilder | @Builder | lombokRequiredArgs | @RequiredArgsConstructor | |
lombokGetter | @Getter | lombokToString | @ToString | |
lombokSetter | @Setter | lombokEqualsHashCode | @EqualsAndHashCode | |
lombokSlf4j | @Slf4j |
Flags combine: /com.app.User(String name, int age):lombokData:lombokBuilder
→ a @Data @Builder class.
Source-set and module ([subdir])
- a source-set name places the class in the current module's
src/<name>/java—[test]mirrors the package intosrc/test/java; - a subproject name (multi-module) targets that module directly —
[core]or[core/test]for its test sources.
When an absolute package you pick already lives in another module (completion offers packages from every subproject), jc asks which module to create the class in; a brand-new package goes to the current module.
Completion and the wizard
<Tab> completes each slot in turn:
- the template and, after
/, existing packages across the whole project (every subproject) — without a leading/, the sub-packages of the current file's package instead; either way you can still type a new package by hand; [subdir]after a template — source-sets and module names;- the flags once the class path is given;
- after
extends/implements— class/interface names resolved live from jdtls.
<p>N (or class_prompt = "wizard") runs the same thing as a step-by-step
vim.ui flow instead of the one-liner.
From a reference under the cursor
With the cursor on a class name the code refers to but that doesn't exist yet,
<p>nc (:JCgenerateClassFromCursor) picks up that name, asks for a package
(every existing project package, the current one, or a new one — and the module
on a multi-module project), then drops you in the DSL prompt pre-filled with
[module]:/pkg.Name so you can still add extends, fields or flags before
creating it.
🧩 Templates
Built-in: class, interface, enum, record, annotation, exception,
main, singleton, servlet, junit, junit5, entity, service,
component, repository, controller and the android_* family.
The entity template carries @Entity and an @Id id, and annotates each
prompt field with @Column(name = "<snake_case>"). Imports are left to
organize-imports (run automatically after creation), so it works whether your
project uses jakarta.* or javax.*.
Custom templates
Point templates_dir at a folder of <name>.lua files. Each returns either
a declarative spec table (recommended — describe only the essence, the engine
builds the rest) or a function(opts) -> string for full control.
A Lombok DTO is just imports + an annotation, no skeleton to repeat:
-- ~/.config/nvim/jc-templates/dto.lua
return {
imports = { "lombok.Data" },
annotations = { "@Data" },
}
dto:/com.app.User(String name, int age) then produces a @Data class with the
package, declaration and fields filled in.
Spec fields (all optional): kind (class/interface/enum/annotation/
record), modifiers, extends, implements, imports, annotations,
body, pre_fields (members before the prompt fields), field_annotation
(function(field) -> string). imports/annotations/body may each be a
string, a list or a function(opts). User input for extends/implements
overrides the spec defaults. opts: name, package, fields
({ mod, type, name }), extends, implements.
🧷 Snippets
jc.nvim ships an optional set of Java field/modifier and NetBeans-style
snippets (snippets/java.json, VS Code format). jc doesn't run a snippet engine
— point your own at the folder. The prefix scheme: p/P = private/public,
s = static, f = final; a lowercase type initial is a primitive (psfl →
private static final long), an uppercase one a wrapper (psfL → … Long).
Plus fori, forl, ife, dowhile, whileit, inst, pst, soutv,
runn, lazy.
Wiring it into your snippet engine
Point the loader at the plugin's snippets/ directory (adjust the path to your
plugin manager; the lazy.nvim location is shown):
-- LuaSnip
require("luasnip.loaders.from_vscode").lazy_load({
paths = { vim.fn.stdpath("data") .. "/lazy/jc.nvim/snippets" },
})
-- nvim-cmp + vsnip, blink.cmp, or native vim.snippet users: load the same
-- VS Code snippet folder however your engine consumes `package.json` bundles.
🧪 Test runner
jc.nvim ships a neotest adapter.
neotest is an optional dependency — without it the plugin works as before
and the JCtest* commands warn instead of erroring. Unlike the gradle/maven
adapters, this one resolves the test classpath straight from jdtls and runs the
JUnit Platform Console Standalone
launcher, so there's no build-tool daemon to wait for and gradle/maven/plain
layouts all work the same way. Wire it as in
Installation.

The launcher jar is looked up in ~/.m2; if missing, run :JCtestInstall once
(downloads org.junit.platform:junit-platform-console-standalone via maven) or
set test.console_launcher_path.
Run tests with :JCtestRun (cursor), :JCtestFile, :JCtestSuite,
:JCtestPick, :JCtestLast, or the <p>T* mappings; neotest paints the gutter
green/red and a failed test's diagnostic points at the failing line. Runs open
the summary panel and, via the optional jc consumer, auto-close it after an
all-green focused run (cursor/file/class) — runs with failures stay open.
JCtestRun/JCtestFile also work from a production class: they run its paired
<Class>Test (the same counterpart JCgotoTest uses) when it exists.
The adapter toasts running… at the start and N passed, M failed, K skipped
at the end. Knobs: test.notify, test.open_summary, test.autoclose_summary
(false, or a delay in ms).
Debugging tests — :JCtestDebug (<p>Td) debugs the test at the cursor.
Set your breakpoints first. Needs nvim-dap
and the java-debug bundle in jdtls
(nvim-java bundles it).
By default jc runs its own debugger: it launches the JUnit Platform Console Launcher under a JDWP agent (same classpath and JDK as a normal run) and attaches nvim-dap to it, then reports pass/fail from the XML. Because the console launcher is standalone (its own junit-platform), this works on any project regardless of junit version — including ones where the delegated runner below silently finds 0 tests.
Set test.debug = "external" to delegate to
nvim-jdtls
(jdtls.dap.test_nearest_method) or
nvim-java
(test.debug_current_method) instead — you get their report UI, but their
eclipse test runner bundles its own junit (~5.11) and can discover 0 tests on
a project whose junit differs from the bundle's. "external" falls back to
jc-native when neither plugin is installed.
Classpath, JDK selection and freshness
The classpath is built from jdtls and augmented for correctness:
- test + runtime scopes are unioned — jdtls'
testscope omitsruntimeOnlydependencies ByteBuddy/Mockito need at run time (otherwise "green from the CLI butNoClassDefFoundErrorhere"). - By default (
precompile = false) jc forces a jdtls compile (java/buildWorkspace), uses jdtls'binoutput first and the gradle/mavenbuild/-target/dirs as a fallback. Fast, and fine when jdtls compiles the whole project. - Some projects have classes jdtls won't put in
bin(e.g. certain spring-data repositories) — the run then fails withClassNotFoundExceptionfor a class that exists in the build output. Settest.precompile = true(or toggle with:JCtestPrecompile): jc runsgradle :<module>:testClasses/mvn test-compilefirst and uses the completebuild/-target/output. The compile is async (editor stays responsive, progress in the cmdline), cached per module for the run, and on failure the javac/maven errors go to the quickfix list instead of running the tests. - The run JVM is the configured
java.configuration.runtimesentry matching the highest bytecode version among the module's classes (a 17-compiled test over an 11-target main still runs on 17, as gradle does), falling back toresolveJavaExecutablethen PATHjava.
If jdtls keeps dropping classes from bin, a :JCutilWipeWorkspace + restart
(clean re-import) often makes bin complete again, keeping you on the fast
precompile = false path.
On a multi-module project :JCtestSuite is best-effort: neotest reruns
update_running over the shared tree per sub-run, which can reset an
already-failed class back to running in the summary. Iterate with the focused
:JCtestRun/:JCtestFile, which run as a single neotest run and report
reliably.
🔨 Build runner
Run gradle/maven tasks from the editor, in a dedicated split (q closes it);
compile errors are parsed into the quickfix list.
:JCbuildRun [args]— run with the given args, or prompt (defaulting to the last run, remembered per project). Wide pty so longfile:line:errors aren't wrapped.:JCbuildTask— pick a module (or the whole project), then a task: gradle tasks fromgradlew tasks, or for maven the lifecycle phases, pom profiles/plugin goals and a plugin drill-down (mvn help:describelists every goal of the chosen plugin). The module scopes the run (gradle:module:task, maven-pl module -am).:JCbuildLast— repeat the last task.
Commands run from the reactor root (outermost contiguous pom / settings.gradle), so multi-module builds resolve paths and the reactor correctly.
🧭 Go to file by FQN
JCgotoFqn (and the overridden gf) opens the java source for a
fully-qualified name under the cursor — for jumping out of a terminal, a neotest
output window or a pasted stack trace into the code. It understands:
- a bare FQN
com.foo.Bar(andcom.foo.Bar$Inner→ the outer file); - an FQN with a member
com.foo.Bar.method→ theBarfile; - a line suffix
com.foo.Bar:42; - a stack frame
at com.foo.Bar.method(Bar.java:25)→Bar, line 25 (rejoined even when a narrow terminal wrapped it across two lines).

The file opens in the last window that showed a java buffer (so you can trigger it from a terminal split and land back in your editing window), or a new tab when there is none. The FQN is resolved through jdtls' symbol index (works from any buffer) with a source-tree fallback.
When default_mappings is on, gf is overridden globally and falls back to the
builtin gf when the token isn't an FQN (e.g. a real path). Disable with
setup{ map_gf = false }.
🐞 Debugging
JCdebugAttach / JCdebugLaunch route to a backend:
- the
debug_backendoption /g:jc_debug_backendif set ("dap"or"vimspector"); - auto: nvim-dap installed and vimspector absent → dap;
- fallback: vimspector.
Attach asks for host and port, remembered per project. The adapter port is
resolved from jdtls via vscode.java.startDebugSession, which needs the
java-debug bundle.
🆚 What it adds over plain nvim-jdtls
| Feature | nvim-jdtls | jc.nvim |
|---|---|---|
| Code generation | via code actions | dedicated commands/mappings with field selection |
| Organize imports | code action | smart mode remembering preferred classes per project |
| Class creation | — | DSL prompt / wizard with templates |
| Test runner | — | neotest adapter, classpath from jdtls |
| Build runner | — | gradle/maven task picker → quickfix |
| Debug attach | manual dap config | JCdebugAttach with per-project host/port memory |
| javap/jshell/jol | yes | classpath-aware, built-in |
🩺 Troubleshooting
- Run
:checkhealth jc— it verifies the Neovim version, the attached jdtls client, organize-imports and java-debug availability, the debug backends, classpath resolution and neotest/launcher for the test runner, the jol jar, the treesitter java parser and the data dir. - Go-to-definition returns nothing on a just-created file — it isn't on the
build path yet;
update_config_on_new_filehandles this on first write, or run:JCutilUpdateConfig. - jdtls state looks corrupted / won't start —
:JCutilWipeWorkspacedeletes the eclipse index and restarts (works even with no client attached). - Tests fail with
ClassNotFoundExceptionfor classes that exist — enabletest.precompile(see Test runner).