Extensions for JetBrains'
Kotlin Language Server (kotlin-lsp)
support in Neovim
(>=0.11.0)
Explore the docs »
Report Bug
·
Request Feature
·
Ask Question
- Library and JDK sources (
jar:/jrt:locations from go-to-definition) open as read-only buffers served by the LSP: hover, navigation and semantic highlighting work inside them - Java buffers in a project whose server is running are attached too, like the VS Code client, so unsaved Java edits reach Kotlin analysis at once (
java_files = falseto opt out); a Java file alone never starts the server - Semantic highlighting is refreshed once indexing finishes
- Live-template completions (
main,sout,fori, …) work through the same server-driven insertion as ordinary items (kotlin-lsp v263.4702.0+) - Export workspace to JSON using kotlin-lsp
exportWorkspacecommand - Organize imports with
KotlinOrganizeImportscommand - Format code with
KotlinFormatcommand (uses IntelliJ IDEA formatting) - Toggle diagnostic hints using the
KotlinHintsTogglecommand - Full support for LSP inlay hints with fine-grained configuration
- JDK version specification for symbol resolution
- Support for custom JVM arguments
- Support kotlin-lsp installation from Mason
- Navigate to package folders from package declarations (opens the folder view with oil.nvim using LSP "go to definition")
- "Go to Type Definition" and "Go to Implementation" support (kotlin-lsp v262+)
- Call hierarchy ("incoming/outgoing calls") via
KotlinIncomingCalls/KotlinOutgoingCalls(kotlin-lsp v262.4739.0+) - LSP-driven code folding for Kotlin functions, classes, blocks, imports and multiline comments (kotlin-lsp v262.4739.0+)
- IntelliJ-style file templates (Class, Interface, Data Class, …) via
KotlinNewFromTemplateand on file creation (kotlin-lsp v262.4739.0+) - Configurable build-tool importer (
gradle/maven) via thebuild_tooloption (kotlin-lsp v262.4739.0+) - Maven project import support (kotlin-lsp v262+)
- Multi-project import via the
projectsoption (monorepos, several independent projects in one workspace) (kotlin-lsp v263.4702.0+) - IntelliJ intentions and quick fixes that need editor cooperation — "choose one" menus, copy to clipboard, follow-up rename — through the
intellij/*protocol extensions (kotlin-lsp v263.4702.0+) - Run and debug
mainfunctions from code lenses,:KotlinRunMain/:KotlinDebugMain, nvim-dap configurations or a VS Code.vscode/launch.json, through Gradle or a plain JVM launch (kotlin-lsp v263.4702.0+, requires nvim-dap) - Attach the debugger to a running JVM with
:KotlinDebug -
:KotlinReloadWorkspacere-imports the project without a restart, optionally whenever a build file is saved;:KotlinRestartrestarts the server (kotlin-lsp v263.4702.0+) - Build-import output in a
:KotlinBuildLogbuffer, with notifications when an import starts, fails, or is blocked by an ambiguous build system - Type hierarchy via
KotlinSupertypes/KotlinSubtypes(kotlin-lsp v263.4702.0+) - Move a Kotlin file with imports and references updated: the server implements
workspace/willRenameFiles, so renaming/moving in oil.nvim (lsp_file_methods) fixes up the code - Automatic per-project workspace isolation to prevent LSP conflicts and improve performance
- Use
KotlinCleanWorkspacecommand to delete exactly the index directory the server reports for the current project and restart
- Use
- Per-project LSP configuration via
.kotlin-lsp.luafile - Per-project LSP disabling via marker file
- Create a
.disable-kotlin-lspfile in the project root to prevent the Kotlin LSP from starting (detected automatically by searching upward from the opened file)
- Create a
- Warns when the kotlin-lsp build has expired: builds carry a time-limited licence and the launcher then exits with code 7.
:checkhealth kotlinshows the expiry date.
Note
Version Requirements:
- The plugin launches kotlin-lsp via its
bin/intellij-servernative launcher, which requires kotlin-lsp v262.4739.0+. Older builds that only ship thekotlin-lsp.sh/kotlin-lsp.cmdshim are no longer supported — update your install. - Workspace isolation with the
--system-pathparameter requires kotlin-lsp v0.253.10629 or later. - Zero-dependencies platform-specific builds are supported -- no JDK required by default as the language server bundles its own (kotlin-lsp v261+ or later).
- Inlay hints require kotlin-lsp v261+. The server requests the
jetbrains.kotlinconfiguration section dynamically and only renders hints whose option is answered withtrue— kotlin.nvim implements this handler, so the kotlin_lsp client must be started by kotlin.nvim (see the mason-lspconfig note below). - Code formatting and organize imports require kotlin-lsp v0.253+ with IntelliJ IDEA-based formatting support.
- "Go to Type Definition" and "Go to Implementation" require kotlin-lsp v262+.
- Maven project import is supported starting from kotlin-lsp v262+.
- Call hierarchy, LSP folding, file templates and the
build_tooloption require kotlin-lsp v262.4739.0+. - Intentions with menus/clipboard/rename, run/debug lenses and launching,
:KotlinReloadWorkspace, theprojectsoption and type hierarchy require kotlin-lsp v263.4702.0+. - No separate JDK is required to run the server —
bin/intellij-serveruses its own bundled JBR.
v2 drops support for the legacy kotlin-lsp launcher. The plugin now launches the server exclusively through bin/intellij-server (kotlin-lsp v262.4739.0+); the old kotlin-lsp.sh / kotlin-lsp.cmd shims and the manual java -cp lib/* … fallback are gone.
To migrate:
- Update kotlin-lsp to v262.4739.0 or later:
:MasonInstall kotlin-lsp(or:MasonUpdate). - Remove
jre_pathfrom yourrequire("kotlin").setup{}— the option no longer exists (bin/intellij-servermanages its own bundled JBR). Custom JVM flags still go throughjvm_args. - Run
:checkhealth kotlinto confirm thebin/intellij-serverlauncher is detected.
Tip
Need the old shell-script launcher or a custom jre_path (e.g. you're pinned to a pre-v262.4739.0 kotlin-lsp)? Stay on the v1 line by pinning the last v1 release in your plugin manager, e.g. lazy.nvim: { "AlexandrosAlexiou/kotlin.nvim", version = "v1.4.0" }.
Install the plugin with your package manager:
Dependencies:
- mason.nvim - LSP installer
- mason-lspconfig.nvim - Mason LSP integration
- oil.nvim - File explorer for package navigation (used by "Go to Definition" on package declarations)
- trouble.nvim - Enhanced quickfix/location list UI (required for
:KotlinSymbolsand:KotlinWorkspaceSymbolscommands to display document outline and workspace symbols)
Optional (install and configure separately):
- Debug Adapter Protocol client (nvim-dap). Required for
:KotlinDebug. kotlin.nvim does not install or configure nvim-dap for you — set it up once globally (signs, keymaps, optional UI) and kotlin.nvim will register akotlinadapter on top.
Important
Using mason-lspconfig? Do not let it auto-enable kotlin_lsp. mason-lspconfig's automatic_enable starts
kotlin_lsp from nvim-lspconfig's default config before kotlin.nvim configures it. That client lacks the workspace/configuration handler kotlin-lsp needs, so all inlay hints disappear and kotlin.nvim settings are ignored. Exclude kotlin_lsp and let kotlin.nvim start it:
require("mason-lspconfig").setup {
automatic_enable = { exclude = { "kotlin_lsp" } },
}{
"AlexandrosAlexiou/kotlin.nvim",
ft = { "kotlin" },
dependencies = {
"mason.nvim",
"mason-lspconfig.nvim",
"oil.nvim",
"trouble.nvim",
-- nvim-dap is NOT a kotlin.nvim dependency. Install and configure it
-- separately (signs, keymaps, optionally nvim-dap-ui). kotlin.nvim only
-- registers a `kotlin` adapter and the `:KotlinDebug` command on top.
-- See the "Debugging Support" section below for details.
},
config = function()
require("kotlin").setup {
-- Optional: Specify root markers for multi-module projects
-- Default: { "build.gradle", "build.gradle.kts", "pom.xml", "mvnw" }
root_markers = {
"gradlew",
".git",
"mvnw",
"settings.gradle",
},
-- Optional: JDK for symbol resolution (analyzing your Kotlin code)
-- This is the JDK that your project code will be analyzed against
-- (the server itself runs on bin/intellij-server's bundled JBR)
-- Required for: Analyzing JDK APIs, standard library symbols, platform types
--
-- Usually should match your project's target JDK version
-- Examples:
-- macOS: "/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home"
-- Linux: "/usr/lib/jvm/java-17-openjdk"
-- Windows: "C:\\Program Files\\Java\\jdk-17"
-- SDKMAN: os.getenv("HOME") .. "/.sdkman/candidates/java/17.0.8-tem"
jdk_for_symbol_resolution = nil, -- Auto-detect from project
-- Optional: Specify additional JVM arguments for the kotlin-lsp server
jvm_args = {
"-Xmx4g", -- Increase max heap (useful for large projects)
},
-- Optional: Configure inlay hints (requires kotlin-lsp v261+)
-- All settings default to true, set to false to disable specific hints
inlay_hints = {
enabled = true, -- Enable inlay hints (auto-enable on LSP attach)
parameters = true, -- Show parameter names
parameters_compiled = true, -- Show compiled parameter names
parameters_excluded = false, -- Show excluded parameter names
parameters_context = false, -- Show context parameter hints
types_property = true, -- Show property types
types_variable = true, -- Show local variable types
function_return = true, -- Show function return types
function_parameter = true, -- Show function parameter types
lambda_return = true, -- Show lambda return types
lambda_receivers_parameters = true, -- Show lambda receivers/parameters
value_ranges = true, -- Show value ranges
kotlin_time = true, -- Show kotlin.time warnings
call_chains = false, -- Show call-chain intermediate types (default false)
},
-- Optional: LSP-driven folding (requires kotlin-lsp v262.4739.0+)
-- Enabled by default; set folding.enabled = false to opt out.
folding = { enabled = true },
-- Optional: build-importer preference (requires kotlin-lsp v262.4739.0+)
-- Mirrors the VSCode `intellij.buildTool` setting:
-- nil = let the server pick (default)
-- "gradle" or "maven" = force a specific importer
-- "" = none (single-file / no build system)
-- build_tool = "gradle",
-- Optional: import several projects from one workspace (kotlin-lsp v263.4702.0+).
-- Mirrors the VSCode `intellij.projects` setting. `path` is a build file or
-- project directory, absolute or relative to the workspace root.
-- projects = {
-- { type = "gradle", path = "backend" },
-- { type = "maven", path = "tools/pom.xml", java_home = "/path/to/jdk-17",
-- env = { MAVEN_OPTS = "-Xmx1g" }, system_properties = { ["skip.tests"] = "true" } },
-- },
-- Optional: re-import when a build file (build.gradle(.kts), settings.gradle(.kts),
-- pom.xml) is saved: "ask" (default), "always" or "never".
reload_workspace = { on_build_file_save = "ask" },
-- Optional: run/debug code lenses above `main` functions (kotlin-lsp v263.4702.0+)
code_lens = {
enabled = true,
-- The server titles lenses with VS Code codicons ("$(play) Run"); these are
-- shown instead (Nerd Font glyphs by default). `icons = false` shows text only.
-- icons = { play = "", debug = "" },
align = true, -- draw the lens at the line's indent instead of at `main`
},
-- Optional: launching programs (requires nvim-dap)
dap = {
console = "integratedTerminal", -- or "internalConsole" (output in the dap REPL)
build_before_run = true, -- plain JVM launches: run the server's build command first
configurations = true, -- add default entries to dap.configurations.kotlin/java
},
-- Optional: attach kotlin_lsp to Java buffers of a project whose server is already
-- running (the VS Code client does this so unsaved Java edits reach Kotlin analysis
-- immediately). The Kotlin server offers no Java features itself, and a Java file
-- alone never starts it. Set false if you want Java buffers left to jdtls only.
java_files = true,
-- Optional: JetBrains data sharing / region, as asked by the VS Code extension on first
-- start. Unset = share nothing. data_sharing: "none" | "anonymous" | "full";
-- region: "africa" | "americas" | "apac" | "china" | "europe" | "middle_east" | "oceania"
-- data_sharing = "none",
-- region = "europe",
-- Optional: disable the RocksDB write-ahead log of the index (VSCode
-- `intellij.disableRocksDBWriteAheadLog`)
-- disable_rocksdb_wal = false,
-- Optional: file templates for new Kotlin files (requires kotlin-lsp v262.4739.0+)
-- When you create a new .kt file the plugin asks the server to interpolate the
-- chosen template. Pass a table of name → Velocity template to override the
-- defaults (Class, File, Interface, Data Class, Enum, Annotation, Object).
-- Set { enabled = false } on the table to disable the prompt entirely.
-- file_templates = {
-- enabled = true,
-- -- Class = "package ${PACKAGE_NAME}\n\nclass ${NAME} {\n\t|\n}",
-- },
}
end,
},
Since different projects may target different JDK versions or require different settings, kotlin.nvim supports per-project configuration via a .kotlin-lsp.lua file in your project root.
Create a .kotlin-lsp.lua file in your project root:
-- Project-specific Kotlin LSP configuration
return {
-- This project targets JDK 21
jdk_for_symbol_resolution = "/Library/Java/JavaVirtualMachines/jdk-21.jdk/Contents/Home",
-- Override inlay hints for this project
inlay_hints = {
enabled = false, -- Disable inlay hints for this specific project
},
-- Project-specific JVM args
jvm_args = {
"-Xmx2g", -- Less memory for smaller project
},
}- Global config in your Neovim setup (applies to all projects)
- Project config in
.kotlin-lsp.lua(overrides global for that project) - Project settings are merged with global settings, with project taking precedence
- After editing
.kotlin-lsp.lua, run:KotlinRestart. Options that only affect the import (build_tool,projects,jdk_for_symbol_resolution) are picked up by:KotlinReloadWorkspacetoo, which keeps the server and its indexes.
Multi-project workspace with different JDK targets:
~/projects/
├── legacy-app/ # Uses JDK 11
│ └── .kotlin-lsp.lua # jdk_for_symbol_resolution = "/path/to/jdk-11"
└── modern-app/ # Uses JDK 21
└── .kotlin-lsp.lua # jdk_for_symbol_resolution = "/path/to/jdk-21"
Project with specific memory requirements:
-- .kotlin-lsp.lua for large monorepo
return {
jvm_args = { "-Xmx8g" }, -- More memory for large codebase
}Tip
Add .kotlin-lsp.lua to your .gitignore if settings are developer-specific, or commit it if the entire team should use the same configuration.
Since the Kotlin language server is under heavy development, it may not fully support all project types or setups yet. If you run into issues with a specific project, you can disable the LSP for that project by creating a .disable-kotlin-lsp marker file in the project root:
cd /path/to/your/kotlin/project
touch .disable-kotlin-lspThe plugin searches upward from the opened file's directory, so it will find the marker regardless of your current working directory. The file can be empty — only its presence is checked.
Tip
You can also disable the LSP for a single buffer by setting the buffer-local variable vim.b.disable_kotlin_lsp = true before the LSP attaches.
When using the Mason-installed kotlin-lsp (v261+), no separate JDK installation is required. The language server includes platform-specific builds with a bundled JRE, providing a truly zero-dependency setup experience.
kotlin.nvim runs the language server through bin/intellij-server, which ships
its own bundled JBR — you do not need to install or configure a Java runtime
to run the server. The only Java-related option is which JDK your code is
analyzed against:
Purpose: Specifies which JDK should be used to analyze your Kotlin code and resolve symbols/APIs.
When to use:
- Your project targets a specific Java version (e.g., Java 17 or 21)
- You need code completion for JDK-specific APIs
- You want symbol resolution against a particular JDK's standard library
- Different projects use different JDK versions
Examples:
-- Project targeting Java 17
jdk_for_symbol_resolution = "/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home"
-- Project targeting Java 21
jdk_for_symbol_resolution = "/usr/lib/jvm/java-21-openjdk"
-- Per-project configuration (in .kotlin-lsp.lua)
return {
jdk_for_symbol_resolution = "/path/to/project-specific/jdk"
}Recommendation: Set this to match your project's target JDK version for accurate symbol resolution.
The latest kotlin-lsp versions offer significantly improved code completion:
- Suggestion ordering on par with IntelliJ IDEA
- ~30% better completion latency
- More relevant and context-aware suggestions
kotlin-lsp does not put the inserted text in its completion items. Each item
carries an empty textEdit plus a jetbrains.kotlin.completion.apply command,
and the server applies the real text + caret afterwards via workspace/applyEdit
and window/showDocument. VS Code's client inserts nothing on accept and lets
the command do the work, so it just works there. Neovim frontends (builtin
completion, nvim-cmp, blink.cmp) insert the item text and run the command, so
the server's edit lands on top and the caret ends up mid-identifier — accepting
App produces Ap|p.
kotlin.nvim fixes this automatically by making Neovim behave like the VS Code client: we turn the client's own insertion into a no-op and keep the apply command, so the server performs the real insertion. You get the full behaviour — text, auto-import, parentheses and caret — in every completion engine (builtin completion, nvim-cmp, blink.cmp). No configuration required.
Note
This relies on the frontend executing the completion item's command (builtin,
nvim-cmp and blink.cmp all do). The proper fix is still upstream returning a
real textEdit.
Tip
Since the server inserts brackets itself (e.g. firstOrNull { }), disable your
frontend's client-side auto-brackets for Kotlin or you'll get an extra
trailing (). For blink.cmp:
completion = {
accept = { auto_brackets = { blocked_filetypes = { "kotlin" } } },
}For nvim-cmp + nvim-autopairs, add kotlin to the confirm_done handler's
filetypes blocklist.
Full support for LSP inlay hints matching the VSCode extension configuration. All hint types are supported with individual toggles.
Minimal configuration (enables all hints with defaults):
require("kotlin").setup {
inlay_hints = {
enabled = true, -- Auto-enable on LSP attach
},
}All settings default to true except parameters_excluded, parameters_context and call_chains. Only specify settings you want to change:
require("kotlin").setup {
inlay_hints = {
enabled = true, -- Master switch: enable/disable all inlay hints
-- Parameter hints (show parameter names in function calls)
parameters = true, -- foo(name: "value", age: 42)
parameters_compiled = true, -- Show parameter names for compiled code
parameters_excluded = false, -- Show hints for excluded parameters (usually false)
parameters_context = false, -- Show context parameter hints (usually false)
-- Type hints (show inferred types)
types_property = true, -- val name: String = "foo"
types_variable = true, -- val count: Int = 42
function_return = true, -- fun foo(): String { }
function_parameter = true, -- fun foo(name: String) { }
-- Lambda hints
lambda_return = true, -- { x -> x * 2 }: (Int) -> Int
lambda_receivers_parameters = true, -- Show receivers and parameters
-- Other hints
value_ranges = true, -- Show hints for ranges
kotlin_time = true, -- Show kotlin.time warnings
call_chains = false, -- someList.filter{}.map{}: List<T> at each step (usually false)
},
}| Setting | Default | Description |
|---|---|---|
enabled |
true |
Master switch to enable/disable all inlay hints |
parameters |
true |
Show parameter names in function calls |
parameters_compiled |
true |
Show parameter names for compiled/external functions |
parameters_excluded |
false |
Show parameter names for excluded parameters |
parameters_context |
false |
Show context parameter hints |
types_property |
true |
Show type hints for properties |
types_variable |
true |
Show type hints for local variables |
function_return |
true |
Show return type hints for functions |
function_parameter |
true |
Show type hints for function parameters |
lambda_return |
true |
Show return type hints for lambdas |
lambda_receivers_parameters |
true |
Show receiver and parameter hints for lambdas |
value_ranges |
true |
Show hints for value ranges |
kotlin_time |
true |
Show kotlin.time package warnings |
call_chains |
false |
Show intermediate result types in method-call chains |
:KotlinInlayHintsToggle- Toggle inlay hints for the current buffer:lua vim.lsp.inlay_hint.enable(true)- Enable inlay hints:lua vim.lsp.inlay_hint.enable(false)- Disable inlay hints
vim.keymap.set('n', '<leader>ih', function()
vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled())
end, { desc = 'Toggle inlay hints' })Note: The KotlinHintsToggle command toggles diagnostic hints (HINT severity diagnostics), while KotlinInlayHintsToggle controls LSP inlay hints. These are two different features.
Inlay hints work by implementing a workspace/configuration handler that responds to server requests for the jetbrains.kotlin configuration section — kotlin-lsp requests configuration dynamically on every inlay hint request rather than using only the initial settings. The server flattens the response into dot-paths and string-matches them against IntelliJ's declarative inlay hint optionIds (hints.parameters, hints.type.property, hints.lambda.return, hints.value.ranges, ...), and only renders hints whose optionId is answered with true. Note that the key names deliberately differ from the JetBrains VS Code extension's package.json for four options — the extension contributes bundle name keys (hints.settings.types.property, ...) that the server never matches.
When enabled (the default on kotlin-lsp v262.4739.0+ and Neovim 0.11+), the plugin wires foldmethod=expr with foldexpr=v:lua.vim.lsp.foldexpr() and sets foldlevel=99 so files open with all folds expanded. Fold ranges (Kotlin functions, classes, blocks, imports, multiline comments) are pulled from kotlin-lsp via the standard textDocument/foldingRange request. To opt out, set folding = { enabled = false } in your setup.
Folding uses standard Vim keymaps — kotlin.nvim does not bind its own:
| Keymap | Action |
|---|---|
zo |
Open fold under cursor |
zc |
Close fold under cursor |
za |
Toggle fold under cursor |
zR |
Open all folds in the buffer |
zM |
Close all folds in the buffer |
zj / zk |
Jump to next / previous fold |
See :help fold-commands for the full list.
kotlin.nvim provides several commands for working with Kotlin code:
| Command | Description |
|---|---|
:KotlinOrganizeImports |
Organize and optimize imports in the current file |
:KotlinFormat |
Format the current buffer using IntelliJ IDEA formatting rules |
:KotlinSymbols |
Show document symbols/outline for the current buffer (displays in trouble.nvim window) |
:KotlinWorkspaceSymbols |
Search for symbols across the entire workspace (displays in trouble.nvim window) |
:KotlinTypeDefinition |
Go to the type definition of the symbol under cursor (v262+) |
:KotlinImplementation |
Go to the implementation of the symbol under cursor (v262+) |
:KotlinIncomingCalls |
Show callers of the symbol under cursor (v262.4739.0+) |
:KotlinOutgoingCalls |
Show what the symbol under cursor calls (v262.4739.0+) |
:KotlinReferences |
Find all references to the symbol under cursor |
:KotlinRename |
Rename the symbol under cursor across the project |
:KotlinCodeActions |
Show all available code actions from kotlin-lsp |
:KotlinQuickFix |
Show quick fixes for diagnostics on current line |
:KotlinInlayHintsToggle |
Toggle inlay hints on/off for the current buffer |
:KotlinHintsToggle |
Toggle HINT severity diagnostics (if sent by the server) |
:KotlinNewFromTemplate |
Pick an IntelliJ-style file template and apply it to the current buffer (v262.4739.0+) |
:KotlinSupertypes / :KotlinSubtypes |
Type hierarchy of the symbol under cursor (v263.4702.0+) |
:KotlinExportWorkspaceToJson |
Export workspace structure to workspace.json |
:KotlinReloadWorkspace |
Re-import the project (resends the initialization options) without restarting the server (v263.4702.0+) |
:KotlinRestart |
Restart the Kotlin language server for all Kotlin buffers |
:KotlinCleanWorkspace |
Stop the server, delete this project's --system-path directory and the index directory the server reported, and restart |
:KotlinBuildLog |
Open the build-tool import / build output buffer |
:KotlinShowLogs |
Open the kotlin-lsp server log (for the current project) and Neovim's LSP log |
:KotlinRunMain [args] |
Run the main function in the current buffer (through Gradle when possible; requires nvim-dap, v263.4702.0+) |
:KotlinDebugMain [args] |
Debug the main function in the current buffer (requires nvim-dap, v263.4702.0+) |
:KotlinDebug [port] |
Attach debugger to a Kotlin/JVM process (JDWP port, default 5005; requires nvim-dap) |
Note
:KotlinSymbols and :KotlinWorkspaceSymbols require trouble.nvim to display results in a clean, interactive window. These commands provide a better alternative to traditional location lists for browsing code structure.
Key Mappings Example:
-- Code actions and quick fixes
vim.keymap.set('n', '<leader>ka', ':KotlinCodeActions<CR>', { desc = 'Kotlin code actions' })
vim.keymap.set('n', '<leader>kq', ':KotlinQuickFix<CR>', { desc = 'Kotlin quick fix' })
-- Go to type definition
vim.keymap.set('n', '<leader>kt', ':KotlinTypeDefinition<CR>', { desc = 'Go to type definition' })
-- Go to implementation
vim.keymap.set('n', '<leader>ki', ':KotlinImplementation<CR>', { desc = 'Go to implementation' })
-- Organize imports
vim.keymap.set('n', '<leader>ko', ':KotlinOrganizeImports<CR>', { desc = 'Organize Kotlin imports' })
-- Format buffer
vim.keymap.set('n', '<leader>kf', ':KotlinFormat<CR>', { desc = 'Format Kotlin buffer' })
-- Show symbols
vim.keymap.set('n', '<leader>ks', ':KotlinSymbols<CR>', { desc = 'Show document symbols' })
-- Find references
vim.keymap.set('n', '<leader>kr', ':KotlinReferences<CR>', { desc = 'Find references' })
-- Rename symbol
vim.keymap.set('n', '<leader>kn', ':KotlinRename<CR>', { desc = 'Rename symbol' })
-- Toggle inlay hints
vim.keymap.set('n', '<leader>kh', ':KotlinInlayHintsToggle<CR>', { desc = 'Toggle inlay hints' })
-- Show LSP logs
vim.keymap.set('n', '<leader>kl', ':KotlinShowLogs<CR>', { desc = 'Show Kotlin LSP logs' })
-- Debug
vim.keymap.set('n', '<leader>kd', ':KotlinDebug<CR>', { desc = 'Debug Kotlin program' })kotlin.nvim integrates with nvim-dap and kotlin-lsp's built-in debug adapter. When a session starts, the plugin sends start_debug_server to kotlin-lsp, which spins up a DAP server, and nvim-dap connects to it. The adapter is registered as kotlin (only if you have not configured one yourself).
Every main function gets two code lenses, Run and Debug (rendered by vim.lsp.codelens; trigger the one under the cursor with vim.lsp.codelens.run()). The same launches are available as commands:
:KotlinRunMain " run main() of the current file
:KotlinRunMain sync --force " with program arguments
:KotlinDebugMain " debug it (breakpoints, stepping, variables via nvim-dap)How the program runs is resolved by the server, exactly like the VS Code extension does it:
- Gradle module: launched through Gradle (
intellij.java.resolveBuildToolLaunch). Gradle compiles and runs; nothing is built separately. - Anything else (Maven, plain JPS): the server returns the runtime paths (
intellij.java.resolveLaunch:javaexecutable, classpath, module path, working directory) and a build command (intellij.java.resolveBuildCommand). kotlin.nvim runs the build first, streaming its output to:KotlinBuildLog, and starts the program only if it succeeds. Setdap.build_before_run = falseto skip the build.
Program output goes to a terminal split by default (dap.console = "integratedTerminal", via DAP runInTerminal). Use "internalConsole" to stream it into the nvim-dap REPL instead.
The resolution runs in the adapter's enrich_config hook, so it applies to every launch configuration of the kotlin type, not only to the lenses: dap.continue() offers "Launch main class", "Launch main class (plain JVM)" and "Attach to JVM" out of the box (disable with dap.configurations = false), you can add your own to dap.configurations.kotlin, and a .vscode/launch.json written for the VS Code extension works unchanged. Its intellij_jvm, intellij_gradle and intellij_debugger types are routed to the same adapter, and file, classPaths, modulePaths, moduleName, javaExec, projectPath, sourceSet and gradleArgs are honored the way the extension honors them. Only mainClass is required.
-- dap.configurations.kotlin entry
{
type = "kotlin", request = "launch", name = "Run server",
mainClass = "com.example.ServerKt",
launcher = "gradle", -- "auto" | "gradle" | "jvm" (default "jvm" here, "auto" for the lens)
args = { "--port", "8080" }, vmArgs = { "-Xmx1g" }, env = { APP_ENV = "dev" },
build = true, -- plain JVM launches: compile first (default dap.build_before_run)
}Programmatic use, including forcing a plain JVM launch for a Gradle module:
require("kotlin.dap").run_main({
mainClass = "com.example.MainKt",
noDebug = true, -- false = debug
launcher = "jvm", -- "gradle" | "jvm" | nil (server decides)
args = { "--verbose" },
vmArgs = { "-Xmx1g" },
env = { APP_ENV = "dev" },
})- Start your application with JDWP debugging enabled:
# Gradle
./gradlew run --debug-jvm
# Maven (tests)
mvn test -Dmaven.surefire.debugBoth default to JDWP port 5005.
-
Open a Kotlin file to activate kotlin-lsp
-
Set breakpoints and attach the debugger:
:KotlinDebug " prompts for port (default 5005)
:KotlinDebug 5005 " attach to port 5005 directly
:KotlinDebug 8000 " attach to a custom portFor breakpoint, stepping, REPL, and variable inspection workflows, see :help dap.txt. These are standard nvim-dap features and are not Kotlin-specific.
Note
nvim-dap is an optional dependency. If it is not installed, DAP features are silently skipped and the rest of the plugin works normally.
:KotlinReloadWorkspaceasks the server to re-import the project (intellij/reloadWorkspace, v263.4702.0+), resending the initialization options. The process and its indexes stay. Withreload_workspace.on_build_file_savethe plugin offers ("ask", default) or performs ("always") this whenever you savebuild.gradle(.kts),settings.gradle(.kts)orpom.xml.:KotlinRestartstops and starts the server for all Kotlin buffers.:KotlinBuildLogshows the output of Gradle/Maven imports and of builds run before a launch. Import start, failure and success are also reported as notifications, and so is a folder whose import is blocked because it holds more than one build system (setbuild_tooland reload).
- Library and JDK sources. Go-to-definition into a dependency returns
jar:orjrt:locations. kotlin.nvim fills the buffer through the server'sdecompilecommand (attached sources when the build tool downloaded them, decompiled bytecode otherwise), marks it read-only, and attaches the kotlin_lsp client to it, as the VS Code client's document selector does. Hover, further navigation and semantic highlighting therefore work insidekotlin-stdlib-…-sources.jar!/…/Collections.ktas well. Buffers open before a server restart are re-attached. - Java files. The "Kotlin by JetBrains" bundle ships only the
java-base.lspplugin, which lets Kotlin analysis read Java code and lets Kotlin navigate into Java. It provides no features inside Java documents: definition, references, symbols, semantic tokens and completion all return empty (only hover answers, in library sources). Java features come from thejava.lspplugin of the "Java and Kotlin by IntelliJ IDEA" server. kotlin.nvim attaches to Java buffers anyway, as the VS Code client does, so unsaved Java edits reach Kotlin analysis immediately, but only to a server that a Kotlin file already started for the same project root: opening a Java file alone never starts kotlin-lsp.java_files = falseturns this off. If you run jdtls alongside, filter formatting by client name (vim.lsp.buf.format({ name = "jdtls" })), since both clients advertise it. - Semantic tokens. The server reports indexing through
$/progressbut never asks for a semantic-token refresh afterwards, so buffers opened during indexing kept degraded highlighting until an edit. kotlin.nvim refreshes them when the "Indexing" progress ends. - Live templates. Completion items for
main,sout,foriand friends use the samejetbrains.kotlin.completion.applycommand as ordinary items, so the completion fix above covers them: acceptingsoutyieldsprintln()with the caret between the parentheses.
kotlin-lsp v263.4702.0 exposes most Kotlin intentions from the IntelliJ plugin as code actions. Some of them need the editor: a "choose one of these" menu, copying text to the clipboard, or starting a rename after the edit. kotlin.nvim declares itself a JetBrains-aware client (intellijExtensions) and handles the resulting intellij/chooseAction (shown with vim.ui.select), intellij/copyToClipboard (+ register) and intellij/runEditorCommand (editor.action.rename → vim.lsp.buf.rename, editor.action.triggerSuggest → completion, editor.action.triggerParameterHints → signature help) notifications, so these actions appear in :KotlinCodeActions / vim.lsp.buf.code_action() and work.
Hover text from the server may contain "Go to Super Method"-style links that only VS Code can follow; kotlin.nvim strips the link and keeps the label.
kotlin-lsp builds embed a time-limited licence (for v263.4702.0 it runs out on 2026-10-08). When it lapses the launcher exits with code 7 and the server never starts. kotlin.nvim reports this with a clear message, and :checkhealth kotlin shows the licence status and date, so update kotlin-lsp before that (:MasonInstall kotlin-lsp or a newer GitHub release).
Each project gets its own --system-path directory, ~/.cache/kotlin-lsp-workspaces/<name>-<hash> (%LOCALAPPDATA%\kotlin-lsp-workspaces\<name>-<hash> on Windows), keyed by the resolved project root so two projects with the same directory name do not collide. With kotlin-lsp v263.4702.0+ the server keeps its index inside that directory and reports the exact location (capabilities.experimental.indexDir); :KotlinCleanWorkspace deletes that directory and the reported index directory, and nothing else. Older plugin versions wiped the whole JetBrains analyzer cache, which also held the indexes of every other project.
Note
Upgrading from a version that named the directory <name> only: the old directories under ~/.cache/kotlin-lsp-workspaces/ are no longer used and can be deleted. The first start re-indexes.
The plugin supports two installation methods for kotlin-lsp:
You can easily install kotlin-lsp using Mason with the following command:
:MasonInstall kotlin-lspThis is the recommended approach as Mason handles the installation automatically and includes platform-specific builds with a bundled JRE (zero-dependency installation). No separate JDK installation is required when using the Mason-installed kotlin-lsp.
The plugin launches kotlin-lsp through its bin/intellij-server native launcher (introduced in v262.4739.0), which manages its own bundled JBR. Older builds that only ship the kotlin-lsp.sh / kotlin-lsp.cmd shim are no longer supported — update your install if the launcher is missing.
If you prefer not to use Mason or need to use a specific version of kotlin-lsp, you can install it manually and set the KOTLIN_LSP_DIR environment variable to point to your installation directory:
export KOTLIN_LSP_DIR=/path/to/your/kotlin-lspThe plugin will automatically detect and use your manual installation when the environment variable is set. The install must contain the bin/intellij-server launcher (kotlin-lsp v262.4739.0+):
$KOTLIN_LSP_DIR/
├── bin/
│ └── intellij-server (Unix/macOS launcher; .exe on Windows)
└── lib/
└── ... (jar files)
Important
Download the official kotlin-lsp distribution from GitHub releases to make sure the bin/intellij-server launcher is bundled. Older builds that only ship the kotlin-lsp.sh / kotlin-lsp.cmd shim are no longer supported.
bin/intellij-server manages its own bundled JBR, so there is no JRE to configure. To pass extra JVM arguments (e.g., -Xmx4g) to the server, use the jvm_args option — they are forwarded via the IJ_JAVA_OPTIONS environment variable, which the kotlin-lsp server reads at startup.
require("kotlin").setup {
jvm_args = { "-Xmx4g" },
}Caution
If you use other tools like nvim-lspconfig or mason-lspconfig, make sure to explicitly exclude the kotlin_lsp configuration there to avoid conflicts.