Introduction
For years, the JVM ecosystem has been synonymous with IntelliJ IDEA. Its deep integration with Scala and Kotlin—through specialized plugins and build-tool awareness—set a gold standard for developer experience. Yet, a growing movement of programmers is seeking alternatives: lighter editors, more customizable workflows, and a philosophy often called "vibe coding." This isn't about rejecting powerful tools; it's about choosing the right tool for the right mood and task. As of mid-2026, the landscape of Language Server Protocol (LSP) support for Scala and Kotlin has matured significantly, making Emacs with Eglot a viable, even delightful, escape from the resource-heavy IntelliJ environment.
This article provides an expert guide to setting up Scala and Kotlin LSPs on Emacs using Eglot. You'll learn about the key LSP servers—Metals for Scala and kotlin-language-server for Kotlin—their current capabilities, limitations compared to IntelliJ, and practical configuration tips. By the end, you'll understand how to achieve a productive, low-latency coding experience for JVM languages without sacrificing the power of a modal editor.
Why Escape IntelliJ? The Case for Emacs + Eglot
IntelliJ IDEA is a heavyweight champion. It offers unparalleled refactoring, code analysis, and build integration out of the box. However, it comes with costs: high memory consumption (often 2–4 GB for a medium-sized project), slow startup times, and a GUI that can distract from pure code focus. For developers who spend hours in a terminal, switching to a browser-based IDE or a full-blown desktop application can break flow.
Emacs, by contrast, runs in the terminal or as a GUI, starts in seconds, and uses a fraction of the memory. Combined with Eglot—a minimal, built-in LSP client since Emacs 29.1—you get on-the-fly diagnostics, completion, and navigation without the overhead of a full IDE. The "vibe coding" philosophy emphasizes staying in the zone: using a keyboard-driven interface with minimal distractions. Emacs with Eglot embodies this perfectly.
The LSP Ecosystem for Scala and Kotlin
Scala: Metals
Metals (Scala Language Server) is the de facto LSP implementation for Scala. Developed by the Scala Center and community contributors, it supports Scala 2, Scala 3, and cross-compilation projects. Key features include:
- Automatic build import: Integrates with sbt, Maven, Gradle, and Mill.
- Code completion: Context-aware, with support for implicit conversions and type inference.
- Diagnostics: Compiler errors and warnings presented inline.
- Refactoring: Rename, extract method, and organize imports.
- Go to definition / references: Works across source files and dependencies.
- Hover type hints: Show inferred types and documentation.
Metals communicates with the build tool via BSP (Build Server Protocol), which means it needs a running build server. This is transparent in most setups: when you open a Scala file, Metals starts the build server automatically.
Kotlin: kotlin-language-server
Kotlin LSP support is less mature but rapidly improving. The primary server is kotlin-language-server (often referred to as kotlin-ls), an open-source project by FWiesing. It provides:
- Completion: Basic but functional, with support for standard library and project types.
- Diagnostics: Compiler errors from
kotlinc. - Go to definition: Works within the project and for standard library.
- Hover: Shows type information.
- Formatting: Via
ktlintor IntelliJ formatter (if available).
A major limitation compared to IntelliJ: the LSP cannot run Gradle or Maven tasks directly. It relies on the build tool to produce compiled output and classpath information. For many Kotlin projects (especially Android or multiplatform), this means you still need IntelliJ or a build tool wrapper for full functionality. However, for pure Kotlin/JVM projects (e.g., Spring Boot, Ktor), the LSP works well.
Setting Up Emacs with Eglot
Prerequisites
- Emacs 29.1 or later (Eglot is built-in).
- For Scala:
sbt,coursier, ormillinstalled globally. - For Kotlin:
kotlincompiler andgradleormaven. - Node.js (for
kotlin-language-serverinstallation via npm).
Step 1: Install the LSP Servers
Scala (Metals)
Metals can be installed via Coursier:
cs install metals
Alternatively, you can let Emacs download it automatically via eglot (see configuration below).
Kotlin (kotlin-language-server)
Install via npm:
npm install -g kotlin-language-server
Step 2: Configure Emacs
Add the following to your init.el:
;; Enable Eglot for Scala and Kotlin
(use-package eglot
:ensure t
:hook
((scala-mode . eglot-ensure)
(kotlin-mode . eglot-ensure))
:config
(add-to-list 'eglot-server-programs
'(scala-mode . ("metals")))
(add-to-list 'eglot-server-programs
'(kotlin-mode . ("kotlin-language-server"))))
For Scala, you may want to pass additional arguments to Metals:
(add-to-list 'eglot-server-programs
'(scala-mode . ("metals" "--metals-home" "~/.cache/metals")))
Step 3: Enable LSP Features
Eglot provides:
- Completion: company-mode or built-in completion-at-point.
- Diagnostics: Flymake (built-in) or Flycheck.
- Navigation: xref-find-definitions (M-.), xref-pop-marker-stack (M-,).
- Hover: eldoc (built-in) shows type at point.
Example keybindings:
(define-key eglot-mode-map (kbd "C-c h") 'eldoc-doc-buffer) ;; hover docs
(define-key eglot-mode-map (kbd "C-c r") 'eglot-rename) ;; rename symbol
Practical Tips and Real-World Usage
Case Study: A Scala Web Service
I work on a Scala-based HTTP service using http4s and cats-effect. The project has about 50 source files and uses sbt. With IntelliJ, the project takes about 2 minutes to index on first load, and the IDE consumes ~3 GB of RAM. Using Emacs + Metals:
- First load: Metals downloads the server and starts sbt in the background (about 30 seconds).
- Memory: Emacs + Metals + sbt server uses ~800 MB total.
- Daily workflow: Code completion is instant for local types, slightly slower for deep library types (due to BSP communication). Diagnostics appear within 1–2 seconds after saving. Refactoring (rename, extract method) works reliably.
Case Study: A Kotlin CLI Tool
A colleague uses Kotlin for a command-line data processing tool (no Android dependencies). With kotlin-language-server, they report:
- Completion: Works for standard library and project classes. Lacks smart completion for builders (like
buildMap). - Diagnostics: Shows compilation errors, but sometimes misses type mismatch errors until you run
gradle build. - Navigation: Go to definition works for own code and Kotlin stdlib, but not for Java libraries (e.g., Apache Commons).
The main pain point is that the LSP doesn't integrate with Gradle's incremental compilation, so you must manually run build commands to see all errors.
Comparison: IntelliJ vs. Emacs + Eglot
| Feature | IntelliJ IDEA (with Scala/Kotlin plugin) | Emacs + Eglot + Metals/kotlin-ls |
|---|---|---|
| Startup time | 30–120 seconds (first load) | 5–10 seconds (Emacs), 20–30 seconds (LSP download) |
| Memory usage | 2–4 GB for medium project | 800 MB–1.2 GB (Emacs + LSP + build server) |
| Code completion | Excellent, smart, fast | Good for Scala (Metals), basic for Kotlin |
| Refactoring | Rename, extract, inline, move, etc. | Rename, extract method (Metals only) |
| Build integration | Native sbt/Gradle/Maven support | Via BSP (Metals) or external terminal (Kotlin) |
| Debugger | Built-in visual debugger | No native debugger (use dap-mode or terminal) |
| Customizability | Limited by plugin API | Unlimited (Emacs Lisp) |
| Terminal integration | Built-in terminal | Seamless (Emacs is a terminal app) |
| Cost | Free Community, paid Ultimate | Free and open source |
When to Use Each
- Choose IntelliJ when: You need advanced refactoring (e.g., moving whole packages), a visual debugger, or work with Android/multiplatform Kotlin projects where LSP support is weak.
- Choose Emacs + Eglot when: You value low resource usage, keyboard-centric workflow, and are comfortable with terminal-based development. Ideal for backend Scala services, pure Kotlin JVM projects, or writing Scala scripts.
Advanced Configuration
Handling Multiple Projects
Eglot automatically starts a separate LSP process per project (based on project-root). For monorepos, you may need to configure eglot-workspace-configuration to point to the correct build file.
Example for sbt project with custom build directory:
(setq-default eglot-workspace-configuration
'(:scalameta
(:metals
(:sbtScript "/usr/local/bin/sbt"
:bloopVersion "1.5.6"))))
Using Flycheck Instead of Flymake
Eglot works with Flymake by default, but many prefer Flycheck for richer UI. Add:
(use-package flycheck
:ensure t
:config
(global-flycheck-mode))
(add-hook 'eglot-managed-mode-hook (lambda () (flycheck-mode -1)))
(add-hook 'eglot-managed-mode-hook (lambda () (eglot-flymake-backend 'eglot-flymake-backend)))
Debugging with DAP
For debugging Scala code, you can use dap-mode with the Scala Debug Adapter (based on bloop). This is experimental but functional:
(use-package dap-mode
:ensure t
:config
(dap-auto-configure-mode)
(require 'dap-scala))
Limitations and Workarounds
Scala
- BSP dependency: Metals requires a running build server. If sbt crashes, you lose LSP features until restart.
- Implicit handling: Metals shows implicits via
metalsImplicitscommand, but it's not as smooth as IntelliJ's implicit hints. - Macro expansion: Limited support. For projects heavily using Scala macros (e.g., shapeless, circe), IntelliJ still wins.
Kotlin
- No refactoring:
kotlin-language-serverdoes not support rename or extract methods. You'll need to usesedor manual editing. - Intermittent diagnostics: The LSP may not detect all errors until you run
gradle compileKotlindue to lack of incremental compilation integration. - Android support: Virtually non-existent. Android projects rely on IntelliJ's specialized build system.
The Vibe Coding Philosophy
"Vibe coding" is about aligning your tooling with your mental state. When I need to deeply focus on algorithmic logic or refactor a large codebase, I fire up IntelliJ. But when I'm prototyping, writing tests, or doing exploratory coding, Emacs with Eglot provides a distraction-free environment. The terminal-based workflow means I can stay in the same window for hours, switching between code, terminal, and documentation without ever touching a mouse.
This flexibility is particularly valuable for JVM developers who also work with Python, JavaScript, or Rust. Emacs can handle all those languages with the same LSP framework, reducing cognitive overhead.
Conclusion
Escaping IntelliJ doesn't mean abandoning productivity. With Metals for Scala and kotlin-language-server for Kotlin, Emacs + Eglot offers a lightweight, customizable alternative for JVM development. It's not a full replacement—you'll miss advanced refactoring and debugging—but for many daily tasks, it's more than enough. The key is to embrace the "vibe coding" mindset: choose the tool that fits your current flow, not the one with the most features.
As of 2026, the LSP ecosystem for Scala is mature and reliable. Kotlin is catching up, but still requires occasional trips back to IntelliJ for complex refactors. Start by setting up Metals for your next Scala microservice, and see how it feels to code without the weight of a full IDE. Your fingers—and your RAM—will thank you.
Comments