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

|
7 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.

This is not a Kotlin language extension, and it does not replace one. Completion, hover, errors and the debugger inside a .kt file all come from Kotlin by JetBrains. This extension writes the project model that server refuses to build for a Kotlin Toolchain project, and adds the toolchain's own build, run, test and debug commands around it.

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

Library and cross-module symbols resolve. Completion, hover with documentation, go-to-definition, and the Kotlin compiler's own errors — all from the JetBrains server, which this extension only feeds. 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 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.

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) — the language server. Optional, but it is where every language feature inside a .kt file comes from, and it provides the debugger; this extension offers to install it the first time it describes a project with no server to read the model, and takes "Not now" for an answer permanently.

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.
  • Answer the JetBrains extension's 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.
  • The language 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:

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.

Developing

npm test                              # the parsers, the model, the manifest, the matcher
node tools/schema/generate.mjs        # regenerate the manifest schemas from a toolchain
npx @vscode/vsce package              # build the .vsix

The tests run against output captured from a real toolchain — test/fixtures/ holds the build state, the dependency tree and the compiler errors of a two-module project — because the two things that break a parser written from the documentation are only visible in real output: a boxed WARNING lands in the middle of stdout, and a conflicted dependency prints as 1.9.21 -> 2.4.10.

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 src/schema.js instead.

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.

The Kotlin by JetBrains extension, which provides the language features inside .kt files and the debugger, 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.

Apache-2.0.

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