ktrs
Kotlin formatting and linting in Rust, without starting a JVM.
ktrs is a native replacement for ktfmt and ktlint. It produces the same output, it is 7-190x faster, and it ships as small native binaries with no runtime.
- ⚡ Fast. About 10 ms per file in an editor or pre-commit hook, against roughly a second of JVM startup. On whole projects it uses every core and is still at least 7x faster.
- 🎯 Identical output. Byte-identical to ktfmt 0.64 on 6,121 of 6,123 real-world files (both
tools reject the other two). Lint violations and
--formatoutput match ktlint 2.0 in all three code styles. - 🔌 Drop-in. The
ktfmtandktlintbinaries accept the originals' flags, messages and exit codes, so existing scripts, hooks and CI keep working. - 🧩 Fits your setup. Integrations for GitHub Actions, pre-commit, Spotless, a ktfmt-gradle drop-in plugin, and Neovim, Helix, Zed, Emacs and VS Code.
- 🌳 Built on a faithful parser. ktrs includes a lossless Kotlin parser whose tree matches the Kotlin compiler's PSI node for node.
Try the formatter in your browser in the playground, which runs it as WebAssembly.
Installation
| &&
One install puts three binaries on your PATH: ktrs, ktfmt and ktlint. The install script also
works on Windows under Git Bash; otherwise, download a zip from
Releases.
Usage
Drop-in for ktfmt and ktlint
The ktfmt and ktlint binaries accept the original command lines exactly, so anything that runs
the jars can run them instead:
ktfmtsupports all of ktfmt's flags, plus@argfile,-for stdin and--enable-editorconfig.ktlintsupports!negation in patterns,-F,--stdin,--patterns-from-stdin,--baseline,--editorconfig, every built-in reporter, and the git hook subcommands.- ktlint implements 2.0.0-ALPHA-4, with its engine and all 105 standard rules. JVM rule sets and
reporters (
-R,artifact=) can't be loaded.
Integrations
GitHub Actions
- uses: Hexay/ktrs@v0.3.1 # Linux, macOS and Windows
- run: ktrs fmt --check --style kotlinlang
- run: ktrs lint
pre-commit
No Rust needed: on first run, the hook downloads the release binaries for its rev.
- repo: https://github.com/Hexay/ktrs
rev: v0.3.1
hooks:
- id: ktrs-fmt # also: ktrs-fmt-check, ktfmt (with ktfmt's flags in `args`)
args:
- id: ktrs-lint # also: ktrs-lint-format, ktlint (with ktlint's flags in `args`)
Editors
Editors pass the buffer on stdin. --stdin-name gives ktrs the file's path so .editorconfig
applies. Add --style google or --style kotlinlang if you need them.
Neovim (conform.nvim):
formatters_by_ft = ,
formatters = ,
Helix (languages.toml):
[[]]
= "kotlin"
= { = "ktrs", = ["fmt", "-"] }
= true
Zed (settings.json):
"languages":
Emacs (apheleia):
(push '(ktrs . ("ktrs" "fmt" "--editorconfig" "--stdin-name" filepath "-")) apheleia-formatters)
(setf (alist-get 'kotlin-mode apheleia-mode-alist) 'ktrs)
VS Code (Custom Local Formatters):
"customLocalFormatters.formatters":
Gradle
ktfmt-gradle drop-in. The io.github.hexay.ktrs plugin replaces
ktfmt-gradle 0.27.0. Change only the plugin id. The
ktfmt { } block, the ktfmtCheck/ktfmtFormat* tasks, --include-only and the
com.ncorti.ktfmt.gradle.* imports keep working.
plugins {
id("io.github.hexay.ktrs") version "0.3.1" // was: id("com.ncorti.ktfmt.gradle") version "0.27.0"
}
Spotless. KtrsStep replaces ktfmt() (Spotless 7+):
buildscript {
repositories { maven("https://hexay.github.io/ktrs/maven") }
dependencies { classpath("io.github.hexay:ktrs:0.3.1") }
}
spotless {
kotlin {
addStep(io.github.hexay.ktrs.spotless.KtrsStep.create(io.github.hexay.ktrs.KtrsOptions.kotlinlang()))
}
}
Until the plugin is on the Gradle Plugin Portal, add the repository in settings.gradle.kts:
pluginManagement {
repositories {
gradlePluginPortal()
maven("https://hexay.github.io/ktrs/maven")
}
}
The io.github.hexay:ktrs jar has no dependencies. It bundles the native binaries for Linux, macOS
and Windows (x86-64 and ARM) and keeps long-lived ktrs serve processes, so a build starts the
binary once, not once per file.
KtrsOptionsmirrors ktfmt's options: start frommeta(),google()orkotlinlang(), then chainwithMaxWidth,withBlockIndent,withContinuationIndent,withRemoveUnusedImports,withTrailingCommasandwithEditorConfig(true).- From other JVM code,
Ktrs.create()returns a thread-safe formatter:ktrs.format(code, KtrsOptions.google()). - The plugin accepts
useClassloaderIsolation,processIsolationJvmArgsandktfmtClasspathbut ignores them.debuggingPrintOpsAfterFormattingonly logs a warning. - To use a different binary from the bundled one, set the Gradle property
ktrs.executable. - Without the jar, Spotless's generic step runs the binary once per file:
nativeCmd("ktfmt", "/path/to/ktfmt", listOf("--kotlinlang-style", "-")).
Performance
Each tool is run from the command line the way users run it: same flags, same files, identical output. Timings are wall time, the median of 5 alternating runs after a warm-up.
Formatting compares the ktfmt binary with the ktfmt 0.64 jar on a Windows 11 laptop (Intel Core
Ultra, 22 threads, JDK 21):
| Scenario | ktrs | ktfmt 0.64 | Speedup |
|---|---|---|---|
| Editor: one 8 KB file on stdin | 16 ms | 1.49 s | 96x |
| Pre-commit: 10 changed files | 26 ms | 2.57 s | 99x |
| CI check on okhttp (617 files) | 316 ms | 9.08 s | 29x |
| Format okhttp in place | 465 ms | 9.58 s | 21x |
| Format okhttp in place, 1 core | 1.48 s | 40.79 s | 28x |
| Format 7 projects (6,123 files, 31 MB) | 3.69 s | 24.55 s | 7x |
Linting compares the ktlint binary with the ktlint 2.0.0-ALPHA-4 jar on Linux (Xeon E-2136, 10
CPUs, JDK 21):
| Scenario | ktrs | ktlint 2.0 | Speedup |
|---|---|---|---|
| Editor: one 8 KB file on stdin | <10 ms | 1.04 s | >100x |
| Lint one file | <10 ms | 830 ms | >80x |
| Lint okhttp (617 files) | 390 ms | 11.33 s | 29x |
Autocorrect okhttp (-F) |
620 ms | 118.94 s | 192x |
| Lint okhttp, 1 core | 1.35 s · 47 MB | 38.15 s · 344 MB | 28x |
| Lint 7 projects (6,123 files) | 1.79 s · 249 MB | 48.24 s · 515 MB | 27x |
Autocorrect 7 projects (-F) |
3.76 s | 320.12 s | 85x |
JVM startup dominates small runs. On large runs ktrs is still several times faster per core, and it uses every core. The binary is a few MB with no runtime, compared with a 71 MB jar plus a JRE.
- Corpus. The 7 projects are okhttp, kotlinx.coroutines, nowinandroid, ktlint, ktfmt, Exposed
and ktor, pinned in
corpus/REVISIONSand fetched bytools/fetch-corpus.sh. - 1 core. Both processes are pinned to one CPU from launch. The JVM then sizes its GC and JIT threads for one CPU, as it would in a 1-CPU container, and its JIT competes with the work.
- Identical output. ktfmt rejects 2 of the 6,123 files, Exposed's
{{packageName}}code-generator templates, and ktrs rejects them with the same error. - Reproduce. Formatting:
py -3 tools/bench/e2e.py. Linting:tools/bench/lint-e2e.sh, with results and the comparison against ktlint 1.8 and ktlint-rs in research/20.
Spotless. spotlessApply on okhttp's 573 files gives identical output. The figures are the
formatter's share, after subtracting a Spotless run that only trims whitespace:
| Spotless step | ktrs (KtrsStep) |
ktfmt 0.64 (ktfmt()) |
|---|---|---|
| Fresh Gradle daemon, as in CI | ~4 s | ~52 s |
| Warm daemon, repeated runs | ~2 s | ~6 s |
How correctness is checked
Parity with the original tools is the spec. The ported test suites run in CI, and the corpus diffs
(cargo corpus-diff, cargo fmt-diff, cargo lint-diff) compare against the real tools on ~6,000
files:
- Parser. The tree must match the Kotlin compiler's PSI (
DebugUtil.psiToString) on the compiler's own test fixtures and on every corpus file. - Formatter. ktfmt's test suite is ported. The output is also diffed byte for byte against the ktfmt jar on the corpus in the meta, google and kotlinlang styles.
- Linter. ktlint's rule tests are ported. Violations and
--formatoutput are diffed against the ktlint jar on the corpus in thektlint_official,intellij_ideaandandroid_studiocode styles, with and without experimental rules. - CLIs. The
ktfmtandktlintbinaries are compared with the jars on stdout, stderr, exit code and written files across a scenario suite.
Contributing
CLAUDE.md lists the crate layout and every parity gate. Design notes are in
research/.
License
Dual-licensed under MIT or Apache-2.0, at your option. ktrs contains code and test data ported from the Kotlin compiler, the IntelliJ Platform, ktfmt, google-java-format, ec4j (Apache-2.0), ktlint and ktfmt-gradle (MIT). See NOTICE.