Metals: "Fast, Correct — choose two"
Tomasz Godzik, Krzysztof Romanowski
@ VirtusLab
Virtuslab is a company focused on improving companies workflows via improvements to the Developer Experience
What do we do?
Most prominent Metals issues
01
02
03
04
MBT - Metals Build Tool
Fast IDE features
Improved Java Support
05
Questions
What is Metals?

Metals is a language server protocol server for Scala and Java.





Uses Language Server Protocol to provide IDE features in any editor that implements the LSP client
Based on JSON RPC
Widely supported by multiple editors, de facto standard for most languages
Metals LSP Server
Our goal

- A lot of the editors' IDE features only available after a successful build or import
- Large monorepo setups can have even a whole host of problems
Most prominent problems
Our Goal for Metals v2
Make the IDE useful from the moment you clone a repo.
Main Metals v2 was worked on in Databricks by Ólafur Páll Geirsson, Iulian Dragos and others in the team.
"How many of you have waited 5+ minutes for IDE features after opening a project?"
-
Metals Build Tool symbol index
-
IDE Features available from the start
- Better Java Language Support
Main changes
Each feature reinforces the others — together they make Metals viable for large, mixed-language monorepos.
Metals Build Tool

Wait! Another build tool!?

It's not really a build tool
It's about getting the minimal amount of information from the build to somewhere Metals can access it directly.
Symbol search depended on the BSP build server. We needed to connect to the build server (could be different from the build tool)
We had to wait to get all `src/main/scala` etc.
Before (current main)
Before (current main)
Editor opened
Build import
(1-5 min)
Build import
Build import
Build import
Build server start (20s-2min)
Build import
Build import
Build import
Not imported
Already imported
Indexing
(30s - 3min)
Build import
Build import
Build import
Ready to serve requests
Build import
Build import
Build import
Compilation (30s - 10min)
Build import
Build import
Build import
Future (main-v2)
Self-contained index, zero build dependency. Can work as soon as it's indexed.
Future (main-v2)
Editor opened
Build import
(1-5 min)
Build import
Build import
Build import
Build server start (20s-2min)
Build import
Build import
Build import
Not imported
Already imported
Indexing
(30s - 3min)
Build import
Build import
Build import
Ready to serve requests
Build import
Build import
Build import
Compilation (30s - 10min)
Build import
Build import
Build import
How MBT works
git ls-files --stage
~200-300ms
Each file checked if modified by OID
Index each file using existing fast indexers
Create IndexedDocument
Overwrite existing index.mbt on close
Bloom filters
Query: "UserSer"
│
├─ File A → MAYBE match → compile it
├─ File B → NO match → skip
├─ File C → NO match → skip
└─ File D → MAYBE match → compile it
Result: only 2 of 4 files opened instead of all 4
Bloom filters
At scale (10k+ files), this means orders of magnitude fewer files scanned.
Query: "UserSer"
│
├─ File A → MAYBE match → compile it
├─ File B → NO match → skip
├─ File C → NO match → skip
└─ File D → MAYBE match → compile it
Result: only 2 of 4 files opened instead of all 4
No startup time IDE - interactive features

git clone
Full IDE:
- Hover
- Complete
- Navigate
- References
Build import
Build import
Build import
Open editor
Ideal scenario
If we don't have anything we put all sources into the presentation compiler.
This means that even in projects with broken build tool you should be able to work on your code.
How does it work?

And it does work in a lot of cases.
How does it work?
Java support

One of the main goals in Metals 2 to was to make it viable for large mixed codebases.
Hence we needed to step up out Java support as well.
Java support
We decided to implement most of the basic features of LSP for Java.
No complex refactors, since most likely LLM era makes them redundant.
Java support
| Feature | Status | Powered by. |
|---|---|---|
| Completions | ✅ | Presentation compiler |
| Hover | ✅ | Presentation compiler |
| Signature Help | ✅ | Presentation compiler |
| Diagnostics | ✅ | Presentation compiler |
| Semantic Highlighting | ✅ | Java Semanticdb |
| Selection Range | ✅ | Javac AST |
| Document Symbols | ✅ | Javac AST |
| Go to Definition | ✅ | SemanticDB + mbt |
| Find References | ✅ | mbt + Bloom filters |
But how to make it efficient in large codebase?
We use google turbine which allows us to define stubs for each file without compiling method bodies.
We get in-memory classfiles which the javac compiler can use as a normal classpath
But how to make it efficient in large codebase?
Caveat: It does not support project Lombok or annotation processors currently.
Google Turbine
example/File.java (edited)
example/Other.java
...
Classfiles stubs: example/Other.class, example/Other2.class
File.java,
Google Turbine
Presentation compiler
Javac outline compiler, compiles the file fully
example/File.java (edited)
example/Other.java
...
Classfiles stubs: example/Other.class, example/Other2.class
File.java
Google Turbine
Presentation compiler
Javac outline compiler, compiles the file fully
Outline compiler, compiles without method bodies
AlsoEdited.java
By default turbine will run every minute, so we are back to classfiles once that completes
Google Turbine
Demo
Summary
Java Support
- mostly feature complete
- turbine
- outline compiler
Fast IDE features
- sourcepath fallback
- mbt fallback
- better crash resilience
MBT Index
- bloom filters
- protobuf
- persists to disk
- can use mbt.json
How do I actually use it?
Build from source on main-v2
or
metals.serverVersion : "2.0.0-M8"
Thank You
Tomasz Godzik
Bluesky: @tgodzik.bsky.social
Mastodon: fosstodon.org/@tgodzik
Discord: tgodzik
tgodzik@virtuslab.com
Metals - choose two
By Tomek Godzik
Metals - choose two
- 26