Skip to content
| Marketplace
Sign in
Visual Studio Code>Programming Languages>Kotlin ToolchainNew to Visual Studio Code? Get it now.
Kotlin Toolchain

Kotlin Toolchain

Optersoft

|
65 installs
| (0) | Free
Makes module.yaml (Kotlin Toolchain) projects resolve in the Kotlin extension, and adds build, run and test tasks.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Kotlin Toolchain for VS Code

What the Kotlin language server is missing when your build is a module.yaml.

Nothing else needs installing. Syntax colours, the compiler's own errors, completion, hover and go-to-definition inside a .kt file come from the small language server bundled in this extension, which reads what the toolchain built. If you also install Kotlin by JetBrains it takes over the .kt files and brings its debugger; this extension then writes the project model that server refuses to build for a Kotlin Toolchain project. Either way, the toolchain's own build, run, test and debug commands are here.

The problem

The official Kotlin by JetBrains extension imports Gradle, Maven, Bazel and JPS projects. It does not import Kotlin Toolchain projects. Open one and you get syntax colours and nothing else — println resolves to nothing, an import from a library is an unresolved reference, and a call into a sibling module is a mystery. That is kotlin-lsp#218, open since June 2026.

The server does have a build-system-agnostic importer: hand it a workspace.json and it believes you. Nobody was writing that file for the toolchain.

This extension writes it.

What you get

Language features in .kt files, from this extension alone. Syntax colours arrive the moment a file opens — from the bundled server's own lexer, so data is a keyword before class and a plain name after val, and a "$template" is coloured as the expression it is. Syntax errors are marked as you type, from the same server's parser: an unterminated string, a bracket that never closed, a when branch with no ->. That one needs nothing at all — no build, no compiler, no project — so it works in a project you created a minute ago, and it answers on the keystroke rather than on the save. When the folder opens the same server starts the Kotlin compiler's analysis on the exact classpath the toolchain built with, about two seconds in the background, and the compiler's errors, completion (after a dot from the receiver's type, on a bare prefix from the standard library and every import), hover and go-to-definition follow. With Kotlin by JetBrains installed instead, library and cross-module symbols resolve there: this extension describes the project to it, and hovering println shows its KDoc because the model points at the sources jars the toolchain already downloaded.

Run and Debug, from the manifest and from fun main. A ▶ Run · Debug · Watch · Test lens sits on the product: line of every module.yaml, and ▶ Run · Debug above every top-level fun main. Debugging starts the program under the toolchain — the same compiler, classpath and resources Run uses — with a JDWP agent on a free port, and attaches when the JVM says it is listening.

Run on Save, for machines where a run is worth not repeating by hand. Press Watch and every save of a source file stops the program and runs it again — one terminal, reused, the way a web dev server works. It does not make a run faster, and nothing can: the toolchain has no daemon and no watch mode, so every kotlin run starts a JVM and loads the compiler. What it removes is the rest of the loop — finding the terminal, pressing up, pressing enter — which is the half a person notices on a slow laptop.

  • Saving a test file does not restart anything. kotlin run never compiles the test roots, and writing a test is exactly when you do not want the application torn down.
  • A save in a library module does restart, because the application compiles it in.
  • A Save All is one restart, not one per file. kotlin.runOnSave.delay is the window it coalesces over; raise it where a run is expensive.
  • Compile errors from the watched run go to the Problems panel as they happen.
  • Stop with the status bar item, Ctrl-C in the terminal, closing the terminal, or Stop Watching on the lens. Closing the window stops it too — nothing is left running.
  • kotlin.runOnSave.mode: reload keeps the program running. A save runs kotlin build and swaps the changed classes into the live JVM over its own debug port — no agent, no jar, nothing downloaded by the extension. The swap itself is milliseconds; the build is the wait. Two things the JVM decides: a method that is running keeps its old code until it is entered again (a change inside a running main loop waits for a restart; a change to what the loop calls shows on the next iteration), and a stock JDK swaps method bodies only. With settings.jvm.jdk.distributions: [jetbrains] the toolchain provisions the JetBrains Runtime, whose DCEVM also takes an added function or field. The extension offers that line and runtimeClasspathMode: classes (so a build never rewrites a jar the JVM has open) once per manifest, and writes neither without a click. A change the JVM refuses falls back to a restart; a build that fails leaves the program running and the errors in Problems.

A Test Explorer. The project's tests appear in the Testing view, found by reading the sources, so the tree is there before anything has been run. Running one goes through kotlin test --format=teamcity, so results, durations and failures — with the assertion's own expected/actual diff — come from the engine rather than from scraped console text. Both JUnit/kotlin.test and kotest are understood.

The toolchain's own diagnostics, as you type the manifest. An unknown key, an unresolved module path or a dependency that will not resolve is marked in module.yaml at the token it is wrong at, with the message the CLI itself prints, whenever a manifest is saved.

Tasks for build, run, test, check, package, publish and clean, one per module, with a problem matcher that turns the compiler's boxed diagnostics into entries in the Problems panel.

Completion and go-to-definition for what the manifest refers to. A dependency line offers this project's other modules — as the //ui/utils paths the toolchain wants, not the module names — and every row of libs.versions.toml spelled the way a manifest spells it ($libs.apache.commons.lang3), with the coordinate it points at beside it. apply: offers the templates, and jvm.mainClass offers the classes that really do have a fun main. Ctrl-click any of them to open it: the other module's manifest, the catalog row, the template, the entry point.

The catalogs the toolchain generates, which are in no file your project owns. Turning Compose on does not only add a compiler plugin: it makes $compose.material3 a dependency you can write, and Ktor, Spring Boot, kotlinx.serialization, DataFrame and kotlinx-rpc do the same — 258 keys, offered only in the modules whose settings generate them, because elsewhere the toolchain refuses the build. They come from the toolchain's own catalog, so they are the keys your version has rather than the ones a document once listed.

Maven coordinates as you type one. Every library already in the toolchain's download cache is offered for nothing, and Maven Central is searched for the rest, artifact before its thirty platform variants. Picking one writes group:artifact: and the versions follow, newest first and read from the repository itself rather than from a search index that will happily tell you last year's pre-release is current. Central is the only thing this extension sends anywhere — the characters you have typed, to ask which artifacts start with them — and kotlin.dependencies.searchMavenCentral turns it off, leaving everything else working from your own machine.

Hover on a dependency, whatever kind it is. The module a path reaches and what that module is; the library behind a catalog row; the coordinates of a built-in key, and the setting that keeps it available when that setting is off. Once the project has been built, all of them name the version that was actually resolved — which conflict resolution may have raised above the one written down — and Ctrl-click opens the POM.

The two mistakes the toolchain says nothing about. A jvm.mainClass that names a class with no fun main builds successfully and is never mentioned; a module directory no project.yaml lists is silently never built. Both are marked here, with a fix: set the main class to one that exists, or add the module to the project.

Quick fixes for the CLI's own errors. It knows //ui/nope does not exist; it cannot know //ui/utils does. A wrong module path, catalog key or template is offered the nearest real one.

Add a dependency from an unresolved reference. Unresolved reference 'Widget' in a Kotlin file offers Add dependency on //ui/utils when a sibling module declares it — and writes it into the manifest.

Starting and driving a project. New Project lists the real kotlin init templates and creates one; Run with Compose Hot Reload and Run Custom Command (kotlin do) are in the palette.

Schema-backed manifest editing for module.yaml, project.yaml and *.module-template.yaml: completion and hover on every setting, generated from the toolchain's own type model rather than written by hand — and served by this extension itself, so no YAML extension is needed and no telemetry comes with one. Validation comes from the toolchain, which checks the manifest against the real model rather than against a description of it.

Before the first build

This is the line that separates this extension from IntelliJ IDEA, so it is worth stating plainly rather than feature by feature. IDEA answers nothing about a Kotlin file until the project has been imported and built. Everything in this list works in a project you created a minute ago — no build/ directory, no classpath, no compiler, no JVM — because it is read off the file's own syntax tree:

  • Syntax colours, from the server's own lexer: data is a keyword before class and a plain name after val, and a "$template" is coloured as the expression it is.
  • Syntax errors, on the keystroke rather than on the save: an unterminated string, a bracket that never closed, a when branch with no ->. One mistake is one message, not four.
  • The outline, code folding, and Shift+Alt+→ to grow the selection a syntactic step at a time.
  • Format Document, deliberately narrow: the indentation of lines that begin a statement, trailing whitespace, one final newline — and nothing that is a matter of taste. It refuses a file that does not parse rather than guessing.
  • Keyword completion that knows where the caret is. sealed class and sealed interface where a declaration starts and neither inside a function; vararg in a parameter list and val only in a primary constructor's; else only after an if that has none, catch only after a try, break only inside a loop; in, out and reified between the angle brackets; @get: and the other use-site targets after an @; break@myLoop, return@forEach and this@Outer with the labels that are actually in scope. Nothing at all inside a comment or a string.
  • Postfix templates — ten of them. who.notnull becomes if (who != null) { … }; .val, .if, .for, .return, .println and the rest rewrite the expression in front of the caret.
  • The manifest: schema completion and hover, the module paths and catalog keys a dependency line can take, Find All References, rename, and the built-in catalogs a setting makes available — $compose.material3 is completable the moment compose: enabled is typed, before any save. The two mistakes the toolchain never mentions — a jvm.mainClass with no fun main, a module directory no project.yaml lists — are marked here too. (The manifest diagnostics are the toolchain's own, so those wait for it to have been downloaded, which the first command you run does.)
  • The tasks, the Run/Debug/Watch/Test lenses, and the Testing view's tree, which is read out of your sources rather than out of a test run.

What genuinely waits for a build is what only a compiler can answer: its errors, the completion and hover of names, go-to-definition on code, the version a dependency actually resolved to, and Run on Save's reload. Those arrive from the analysis session, which starts when the folder opens and needs the classpath the build wrote down.

Installing

  1. This extension. It requires no other extension and sends nothing anywhere.

  2. The toolchain — but there is nothing to install on the machine. The kotlin command is an 11 KB script that pins a version and downloads the rest on first use, and a project can carry its own, exactly as Gradle's wrapper does. Open a project without one and the extension offers to add it; Kotlin: Add the Kotlin Wrapper to this Project does the same on demand, and New Project puts one in the folder it creates before doing anything else.

    Both kotlin and kotlin.bat are written, so a colleague on the other platform can build too, and each is verified against the SHA-256 that JetBrains publishes in their own installer. Commit them: a clone then needs nothing at all. An existing wrapper is never touched — it is the project's version pin, and quite possibly older than what is published today.

    A machine-wide kotlin still works if you have one (~/.local/bin/kotlin is found even when it never reached your PATH), but the project's wrapper always wins: a project pinned to 0.11 must not be driven by a 0.12 that happens to be installed.

  3. Kotlin by JetBrains (JetBrains.kotlin-server) — optional, and never asked for. Without it, the bundled server highlights, checks and completes your .kt files. With it, that server takes over the .kt files, this extension describes the project to it, and its debugger is what Debug attaches — debugging is the one thing that needs it.

Starting from an empty folder

Make a folder, open it in VS Code, and press New Project — on the Welcome page under Start a Kotlin project, or from the Command Palette (⇧⌘P / Ctrl+Shift+P) as Kotlin: New Project. That is the whole procedure, on a machine with no Kotlin on it:

  1. If there is no kotlin command anywhere, the wrapper is written into the folder first — that is what makes everything after this step possible.
  2. Pick a template. The list is the real kotlin init one, read from the toolchain itself.
  3. The project is created in the folder you already have open. No second folder to choose, no new window: the manifest opens, the tasks, lenses and the Testing view appear, and the first build downloads the toolchain the wrapper pins.

Commit kotlin, kotlin.bat and the manifest, and a clone of that repository needs nothing installed either.

To add the wrapper to a folder without creating a project — an existing checkout that never had one — run Kotlin: Add the Kotlin Wrapper to this Project.

Open a folder with a module.yaml or project.yaml in it and the rest is automatic.

Three things will make it look broken when it is not, none of them this extension's doing:

  • Trust the folder. VS Code activates no extension in a window you have not trusted, so a project opened in Restricted Mode does nothing at all and says nothing about why.
  • If you use the JetBrains extension, answer its first-run prompts. It asks for a region and a data-sharing preference, and until both are set it never starts its server — no error, no squiggles, an empty log.
  • That server starts when you open a .kt file, not when the folder opens. Its activation events name Gradle and Maven files, so in a toolchain project opening a Kotlin file is what starts it. The model is already written by then, and it is read at startup.

How it works

On activation, and whenever a manifest is saved, the extension describes the project to the language server. The describing is kotlin-lsp's — the small Rust server bundled in this extension, which any editor can run (kotlin-lsp describe <dir>); the extension starts it and reports what it said.

kotlin show modules          the modules and their product types
build/incremental.state/*    what the last build actually compiled
      ↓
build/lsp/workspace.json     the language server's own project format
      ↓
intellij.projects            a workspace-folder setting pointing at it
      ↓
jetbrains.kotlin.reloadWorkspace

The classpath comes from the build, not from a guess. After kotlin build, the toolchain records one JSON file per compile task holding the exact jars the compiler saw — already conflict-resolved, already the right -jvm variant — plus the source roots, the module outputs it depended on, the provisioned JDK and the effective compiler settings. Reading that is both simpler and more accurate than re-deriving it from kotlin show dependencies.

A project that has never been built has no such record, so the extension falls back to the dependency tree mapped onto the shared cache, says so in the status bar, and offers to build. Either way your own sources resolve; libraries need the build.

Settings

kotlin.command The kotlin to use. Empty means the project's ./kotlin wrapper, else the PATH.
kotlin.languageServer.enable Describe the project to the language server at all.
kotlin.languageServer.refreshOnSave Re-describe when a manifest is saved.
kotlin.languageServer.buildBeforeDescribe never, ask (default) or always.
kotlin.codeLens.enable Show Run/Debug above every top-level fun main.
kotlin.testExplorer.enable Show the project's tests in the Testing view.
kotlin.diagnostics.enable Report the toolchain's model errors in the Problems panel.
kotlin.trace Log every command to the Kotlin output channel.

Kotlin: Show Generated Workspace Model opens the file that was written, which is the first thing to look at when a symbol does not resolve.

Limits

JVM only. The language server's own README says Kotlin Multiplatform support is "coming in future releases". Non-JVM modules are left out of the model and named in the log; a kmp/lib gets no language features until the server learns KMP, whatever this extension writes.

The manifest schema is open, not closed. It is generated from the toolchain's own type model, so its keys, types and enum values cannot drift from the CLI that produced it — but an unknown key is reported by the toolchain, with a line and a caret run, rather than by the schema. A manifest may carry keys from a plugin the schema knows nothing about, and rejecting those would be guessing.

Build warnings are not turned into problems, but model warnings are. The task problem matcher reads a compiler error, which carries a │ → file:line:column line; a build warning uses a nested shape with no such line, so it is let go rather than reported at the wrong place. The manifest diagnostics do not go through the matcher and do read that nested shape, so a warning about two modules disagreeing is marked on both lines it names.

A single kotest test cannot be run on its own, so its class is run. --include-test wants a method's fully-qualified name; a kotest test is a string literal in a lambda, and asking for one exits 2 with no tests were discovered. Pressing ▶ on one kotest test therefore runs its spec and reports every result in it — which beats a Run button that silently runs nothing. A JUnit/kotlin.test method really is run alone.

A dependency is only suggested for a top-level declaration in this project. Widget() finds class Widget in a sibling module; a method somewhere that shares the name is not offered, because it is reached through its class. A name that belongs to a library is not offered either — guessing a module would be worse than saying nothing.

A test the finder misses is missing from the tree until the suite is run. Discovery reads the source with no compiler behind it: @Test fun … and the usual kotest builders are found, a test name built at runtime is not. Run the class and the results fill the tree in. The count it found is in the Kotlin output channel.

There may be two Run lenses above fun main. The language server contributes one of its own, unconditionally. It is not the same: it compiles with the server's compiler and runs the classpath the server assembled, which is not what kotlin build produces — no compiler plugins, no KSP, no resources, and jvm.release ignored. Ours runs the toolchain. Set kotlin.codeLens.enable to false if you would rather have only theirs.

Both sides are Alpha. The toolchain renamed itself from Amper three months ago; the language server is at 0.0.x and has renamed settings across releases. Expect a patch release here per upstream release.

A guide, with the measurements

Kotlin in VS Code on Optersoft Academy is the long version of this page, written for a machine that is not fast: what the editor can give you, what it cannot, and where the seconds of an edit-run loop actually go.

It is free to read and needs no account.

The part worth having even if you never install this extension is the measurement. Against toolchain 0.12.0, kotlin show modules — which compiles nothing at all — costs 0.43 s, and an unchanged kotlin run costs 0.55 s. There is no daemon and no watch mode, so every command pays for a JVM and a compiler before it does anything. That is the floor, no editor moves it, and knowing it is what tells you which of the usual advice is worth following. The guide also covers what does not help, so nobody spends an afternoon on JVM startup flags that reach the wrong JVM.

Privacy

This extension collects nothing and sends nothing anywhere. It has no dependency on another extension, which is a deliberate choice rather than an accident: the usual way to make a contributed JSON schema work is to depend on redhat.vscode-yaml, and that extension bundles @redhat-developer/vscode-redhat-telemetry and reports usage to Red Hat through Segment. Nobody should have to opt into a third party's analytics to get completion on a build file, so the schema is served by the bundled kotlin-lsp instead, out of a copy compiled into it.

It still contributes the schema through yamlValidation, so anyone who has a YAML language server of their own gets validation from it as well. That is an option, not a requirement.

Should you install the Kotlin by JetBrains extension, it has its own data-sharing prompt and its own policy. That is between you and JetBrains; this extension only writes a file that it reads, and never suggests installing it.

Licence and privacy

Free to use, for any purpose including commercial work; not open source — see LICENSE. It collects no telemetry and nothing that identifies you; PRIVACY.md lists the few requests it makes and how to switch each one off. The open-source components it bundles are listed, with their licences, in THIRD-PARTY-NOTICES.txt.

Bugs and questions: support@optersoft.com.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft