Revenir au contenu principal
Plateforme

Metals v2 passe en open source : le serveur de langage Java et Scala de Databricks pour les bases de code de plusieurs millions de lignes

Comment combler l'écart en matière d'intelligence de code a permis de faire fonctionner Cursor pour notre monorepo Bazel de 26 millions de lignes

par Ólafur Páll Geirsson, Iulian Dragos et Alessandro Patti

  • Metals v2 repense l'intelligence de code pour les monorepos à grande échelle qui ont dépassé les capacités des serveurs de langage JVM conventionnels.
  • Metals est le serveur de langage Scala officiel depuis des années. Grâce au support complet de Java dans la v2, des entreprises possédant certaines des plus grandes bases de code Java du secteur testent désormais Metals uniquement pour Java.
  • L'AI modifie la valeur relative des fonctionnalités du Language Server Protocol (LSP). Alors que les ingénieurs écrivent moins de code à la main, un faible coût de configuration, un retour rapide et une orientation fiable dans la base de code importent plus qu'une large couverture d'autocomplétion et de refactoring.

La majeure partie du code chez Databricks est désormais écrite par des agents. Pour les moments où les ingénieurs doivent encore intervenir directement, ils se tournent vers des éditeurs légers qui démarrent rapidement et leur permettent de naviguer dans le code avec un minimum de configuration. Pour Scala et Java, cependant, IntelliJ est la référence depuis des années. À l'échelle de notre monorepo, c'était pratiquement le seul éditeur capable de suivre le rythme. Cet article explique comment nous avons construit une solution alternative en étendant Metals, le serveur de langage Scala très utilisé, pour lui apporter un support Java de premier ordre et lui permettre de s'adapter à la taille de notre monorepo. En collaboration avec l'équipe upstream de Metals, nous avons désormais publié Metals v2 en open source afin que toute personne disposant d'une grande base de code Java et Scala puisse associer son agent de codage à un éditeur léger.

Metals v2 est disponible dès aujourd'hui dans Cursor, VS Code et Neovim, avec des instructions d'installation disponibles sur le site web de Metals.

Créer le cercle vertueux de l'IDE

En mai 2025, nous avons commencé à standardiser le flux de travail quotidien des éditeurs de Databricks autour de Cursor. Cursor et VS Code étaient déjà largement utilisés chez Databricks pour le frontend et d'autres tâches non-JVM, et tous deux offraient une excellente prise en charge SSH à distance pour notre environnement de développement basé sur le cloud. Cependant, la plupart de nos services sont écrits en Scala et Java, et y naviguer à l'échelle de notre monorepo était le dernier obstacle ; le problème que nous nous sommes donné pour mission de résoudre.

La standardisation sur un seul éditeur allait bien au-delà des préférences individuelles. Une plateforme IDE unifiée crée un cercle vertueux : les équipes partagent une base commune pour l'exploration et le développement du code, tandis que l'équipe de la plateforme peut concentrer ses investissements au même endroit. Cursor est devenu le principal éditeur pour le travail JVM dans notre monorepo, et nous avons suffisamment consolidé nos outils pour ne pas renouveler la majorité de nos licences IntelliJ cette année.

image2.png
Figure 1 : Une adoption plus large accroît l'effet de levier des investissements dans la plateforme, ce qui améliore l'IDE partagé et renforce l'adoption.

Trois indicateurs montrent l'ampleur de la transition vers Cursor : l'utilisation globale de l'IDE, les événements d'ouverture de fichiers Scala et Java où Metals se trouve sur le chemin critique, et l'adoption en dehors de Databricks.

Utilisation de l'IDE

L'indicateur le plus large est l'utilisation globale de l'IDE. L'adoption de Cursor a d'abord progressé d'elle-même chez Databricks, avant de stagner en septembre 2025 pendant plusieurs mois. La croissance a repris avec le déploiement de Metals v2 et, en juillet 2026, 92 % des utilisateurs actifs hebdomadaires d'IDE ouvraient Cursor, contre 12 % pour IntelliJ. Parmi les ingénieurs qui n'utilisent qu'un seul IDE, 2 400 sont désormais sur Cursor, contre 120 pour IntelliJ.

image3.png
Figure 2 : Utilisation de l'IDE, avril 2025 – juillet 2026. La ligne noire marque la première version interne de Metals v2, qui a déplacé la navigation Scala et Java vers un sourcepath fourni par Metals (analyse approfondie ci-dessous).

Événements d'ouverture de fichiers Scala et Java

Un indicateur plus précis que l'utilisation globale de l'IDE est le ratio de fichiers ouverts dans chaque IDE pour Scala et Java, les langages pour lesquels Metals v2 se trouve sur le chemin critique. Depuis que les premières améliorations majeures de Metals v2 ont été intégrées à Cursor en octobre 2025, la part de Cursor dans les événements d'ouverture de fichiers Scala et Java est passée de 40 % à 78 %. Cette progression est plus lente que l'adoption globale de l'IDE car c'est pour Scala et Java qu'IntelliJ était le plus solidement implanté, mais la part de Cursor continue de progresser mois après mois.

image1.png
Figure 3 : Événements d'ouverture de fichiers Scala et Java par IDE, avril 2025 – juillet 2026.

Première adoption externe

Metals v2 a commencé comme un fork de Databricks, mais l'objectif a toujours été de restituer ce travail à la communauté open source. En collaboration avec l'équipe de Cursor et les principaux mainteneurs de Metals chez VirtusLab, nous préparons une version stable de Metals v2 pour remplacer la version stable v1 actuelle.

Nous contribuons à Metals v2 pour que Cursor fonctionne de manière optimale avec des bases de code Java de plusieurs millions de lignes, en mettant l'accent sur l'amélioration du support de Bazel, du débogage et des tests.—Kevin Niparko, Cursor
L'IA change la façon dont les développeurs utilisent les IDE, et Metals v2 va dans la bonne direction : un démarrage rapide, une orientation fiable dans la base de code et une architecture conçue pour les grandes bases de code. Databricks a validé cette approche à une échelle exceptionnelle, et VirtusLab est ravi de contribuer à apporter ce travail à l'ensemble de la communauté Scala et JVM.—Krzysztof Romanowski, responsable de la productivité du développement, VirtusLab

Les discussions concernant d'autres grandes bases de code JVM ont confirmé la même tendance que celle observée chez Databricks : une demande pour Cursor, VS Code et Neovim avec une solide prise en charge SSH à distance, une pression de la plateforme en faveur d'outils unifiés, et aucune alternative crédible parmi les serveurs de langage JVM existants à l'échelle d'un monorepo.

Nous avons commencé à déployer Metals V2 chez Stripe il y a moins d'un mois, et pourtant nous sommes constamment impressionnés par son excellent fonctionnement dans notre base de code Java, par l'enthousiasme de nos ingénieurs, et par le plaisir que nous avons à collaborer avec les mainteneurs et à apprendre à leurs côtés.—Mahib Hosain, Developer Platform, Stripe

C'est le modèle que nous souhaitons pour Metals v2 : une infrastructure partagée pour les grandes bases de code JVM, maintenue de manière ouverte et façonnée par les entreprises qui en ont besoin pour fonctionner à grande échelle. Le développement continu est dirigé par VirtusLab ; n'hésitez pas à les contacter pour toute question, retour d'expérience ou contribution via le suivi des tickets GitHub ou à l'adresse metals@virtuslab.com.

Analyse approfondie : comment Metals v2 s'adapte à l'échelle

Tout ce qui précède plaide en faveur de Metals v2. La suite s'adresse aux lecteurs qui souhaitent mieux comprendre l'ingénierie derrière l'intelligence de code à faible latence sur un monorepo de 26 millions de lignes.

Puisque les agents écrivent la majeure partie du code, une orientation rapide dans la base de code importait plus qu'une couverture complète de la complétion et de la refactorisation du protocole LSP (Language Server Protocol). Cela a restreint le problème, mais n'a pas rendu la solution évidente : nous devions tout de même fournir une navigation facile à configurer et à faible latence pour un monorepo Scala et Java utilisant Bazel de cette taille, et les LSP prêts à l'emploi n'étaient pas conçus pour supporter une telle échelle. Nous avons dû concevoir l'intelligence de code à l'échelle du dépôt à partir de principes fondamentaux, et considérer l'utilité au démarrage comme une métrique clé à mesurer et à améliorer.

Définir le temps d'accès à l'intelligence initiale (TTII)

Le Time-to-initial-intelligence (TTII) mesure la rapidité avec laquelle l'éditeur devient utile après l'ouverture du dépôt. Nous déclenchons le chronomètre au moment où le serveur de langage s'active et l'arrêtons lorsque les fonctionnalités les plus importantes sont disponibles, sans intervention de l'utilisateur. Ces fonctionnalités critiques incluent la recherche floue des symboles de l'espace de travail, l'accès à la définition et la recherche des utilisations de symboles dans l'ensemble du dépôt.

Les trois couches de Metals v2

Metals v2 est un serveur de langage pour Scala et Java. Nous sommes partis de Metals v1, le serveur de langage Scala officiel, mais atteindre notre objectif de TTII a nécessité bien plus que de simples ajustements marginaux. Nous l'avons forké et avons retravaillé trois couches centrales : l'index du dépôt, les pipelines de compilation Scala et Java, et la frontière d'intégration du build.

  1. Index de dépôt sans build : Metals v2 retire le Build Server Protocol (BSP) du chemin critique de démarrage en indexant directement les sources de l'espace de travail à l'aide de son propre index mbt décrit dans la section ci-dessous. La principale différence par rapport à Metals v1 est que Metals gère désormais le modèle de projet initial au lieu d'attendre que le serveur de build le lui fournisse.
  2. Pipelines interactifs basés sur le compilateur pour Scala et Java : Metals v2 déplace le chargement des symboles d'un classpath fourni par le build vers un sourcepath fourni par Metals, ce qui permet aux diagnostics et à la navigation de refléter plus fidèlement le code réel sur le disque plutôt qu'un instantané obsolète de la dernière compilation réussie. Cela a nécessité de repenser un grand nombre d'hypothèses fondamentales de la base de code v1.
  3. Intégration de build axée sur les métadonnées : Metals v2 utilise toujours BSP, mais avec un contrat plus restreint. Metals v1 utilisait le serveur de build sur le chemin critique de l'éditeur pour les diagnostics, tandis que Metals v2 déplace les diagnostics de routine hors du serveur de build et utilise BSP principalement pour interroger les métadonnées de build : dépendances, sources générées, découverte de tests et lanceurs de débogage. Ce changement facilite l'implémentation d'un serveur BSP pour Metals v2, et notre serveur BSP Bazel interne valide que le modèle s'adapte aux grands monorepos Bazel.

Les sections ci-dessous détaillent chacune de ces couches.

L'index mbt : l'intelligence à l'échelle du dépôt avant la synchronisation du build

mbt signifie Metals Build Tool, et l'index mbt est le principal moteur du TTII ou du contrat « utile immédiatement ». Il s'agit d'un index adressable par le contenu des sources de l'espace de travail : Metals utilise la commande git ls-files --stage pour découvrir les fichiers et les OID des blobs Git afin de décider quelles entrées d'index peuvent être réutilisées. Grâce aux informations à l'échelle du dépôt disponibles avant la synchronisation du build, Metals peut répondre aux questions de « premier kilomètre » :

  • les diagnostics pour les références croisées entre fichiers
  • la recherche floue de symboles
  • l'accès à la définition dans tout le dépôt, à l'exclusion des dépendances externes ou du code généré
  • recherche large de références et d'implémentations grâce à une utilisation créative de filtres de Bloom par document

L'index mbt est en fait une table de hachage associant un fichier source à un résumé local au fichier. Une entrée enregistre les déclarations de package du fichier, les définitions avec leurs emplacements source, et des filtres de Bloom compacts pour les identifiants référencés dans le fichier. Les définitions alimentent la recherche de symboles dans l'espace de travail et le saut à la définition, tandis que les filtres de Bloom permettent à Metals d'exclure rapidement les fichiers qui ne peuvent pas contenir de référence avant d'effectuer des vérifications plus précises. Comme chaque entrée est dérivée d'un seul fichier, les mises à jour incrémentielles restent simples : lorsqu'un fichier change, Metals recalcule l'entrée de ce fichier et la remplace.

Dans notre monorepo, l'index mbt persistant pèse 936 Mo non compressé et contient des informations sur 2,9 millions de symboles répartis sur plus de 142 000 fichiers Scala, Java et Protobuf. Une compilation de benchmark propre prend 22 secondes avec une utilisation complète du CPU sur 32 cœurs, tandis que l'analyse d'un index pré-compilé à partir du disque prend 5 secondes. En production, nous mesurons le TTII comme le temps nécessaire pour démarrer le serveur, charger un index mbt obsolète, le mettre à jour par rapport au dernier état de git ls-files --stage, et redémarrer les compilateurs de présentation Scala et Java : p50 8,7 s, p90 36,7 s. La recherche floue de symboles parmi 2,9 millions de symboles de l'espace de travail est de p50 10 ms, p90 95 ms. Il est possible de réduire encore le TTII, mais avec ces chiffres, ce n'est pas le goulot d'étranglement que nous devons traiter en priorité.

Scala pipeline : traiter 24 millions de lignes de code sur une seule instance de compilateur

Le pipeline Scala est construit autour du compilateur de présentation, un mode du vérificateur de types Scala qui met en cache et réutilise les informations de la table des symboles d'une compilation à l'autre. Cette réutilisation, combinée à la résolution paresseuse des symboles du compilateur, permet à une seule instance de conserver l'intégralité de la base de code Scala de 24 millions de lignes à portée, tout en publiant des diagnostics à p50 0,9 s, p90 8,9 s.

Cette instance unique du compilateur Scala fonctionne selon l'un des deux modes suivants, en fonction de la quantité d'informations dont dispose Metals de la part du serveur de build concernant le fichier en cours d'édition. Avant une synchronisation du build, un compilateur de secours adopte une approche permissive, traitant chaque fichier source du dépôt comme un candidat de dépendance éligible, ce qui rend la navigation immédiatement utile, même dans du code qui ne compile pas encore dans Bazel. Après une synchronisation du build, un compilateur précis se limite aux frontières du classpath et du sourcepath signalées par le serveur de build, ce qui garantit l'exactitude de ses diagnostics et de ses informations de dépendance par rapport au build. Les modes précis et de secours s'appuient tous deux sur les deux mêmes techniques pour maintenir un sourcepath de cette taille gérable :

  • Mode plan pour les sources non ouvertes. Les sources qui ne sont pas ouvertes dans un éditeur sont dépouillées de leurs corps de méthode avant la vérification des types. Cela préserve les signatures de type dont le compilateur a besoin tout en évitant le travail dans les corps de méthode, où se passe la majeure partie du temps de vérification des types.
  • Un index de disposition des sources en mémoire. Les chemins des sources Scala ne codent pas de manière fiable la structure des packages. Metals exécute donc un analyseur pour construire un index léger associant les classes aux emplacements source, puis charge les symboles via cet index au lieu de parcourir le système de fichiers. Le compilateur de secours construit cet index directement à partir de l'index mbt en moins de 50 ms, en réutilisant les données à l'échelle du dépôt que Metals possède déjà plutôt que de réanalyser à partir de la source.

Le mode précis ajoute une troisième technique : il conserve les sources des dépendances transitives sur le sourcepath, de sorte que les modifications apportées à des fichiers provenant de différentes cibles Bazel soient immédiatement reflétées dans l'éditeur sans attendre que Bazel produise un nouveau classpath, une contrainte qui freinait Metals v1.

Conserver une base de code de 24 millions de lignes dans une seule instance de compilateur de présentation va bien au-delà de ce pour quoi le compilateur Scala a été conçu à l'origine. Metals v2 y parvient grâce à deux modes de compilateur basés sur un ensemble partagé de techniques pour mettre à l'échelle le sourcepath. Le saut à la définition, la fonctionnalité la plus utilisée de l'éditeur, s'exécute sur l'ensemble du dépôt à p50 7 ms, p90 575 ms. Le Scala étant le langage dans lequel travaillent la plupart de nos ingénieurs, ce pipeline était l'élément clé qui devait fonctionner pour que Cursor devienne une véritable alternative à IntelliJ dans notre monorepo.

Pipeline Java : traiter un million de lignes de code Java par seconde avec Turbine

Metals v2 implémente la surface LSP Java directement sur les API de javac. Nous avons envisagé de réutiliser un serveur de langage Java existant (implémentations basées sur JDT et NetBeans), mais tous deux sont centrés sur le build de la même manière que Metals v1, le couplage exact que la v2 visait à éliminer. S'appuyer sur javac a plutôt permis à Java de partager le sourcepath et le modèle de synchronisation de build de Metals v2 avec Scala, qui constitue toujours la majeure partie de notre monorepo, et la surface Java dont nous avions besoin était suffisamment restreinte pour que nous puissions la maintenir nous-mêmes.

Les API de javac ont fourni un accès suffisant au compilateur pour implémenter les diagnostics, la navigation, la coloration sémantique et d'autres méthodes LSP clés, et elles ont bien résisté sur du code partiellement cassé, ce qui est important pour le flux de travail d'un éditeur interactif. Comme pour Scala, nous avons délibérément restreint la portée des fonctionnalités d'édition active telles que les refactorisations et les complétions.

Le principal problème d'évolutivité de cette architecture est apparu dans des fichiers qui déclenchaient des performances pathologiques lors de la phase « enter » de javac en important de manière transitive des millions de lignes de code au niveau de la couche de plan des symboles. Metals v2 résout ce problème avec un mode javaSymbolLoader: "turbine-classpath", activé par défaut, qui utilise une version modifiée du compilateur d'en-têtes Turbine pour produire un classpath de dépôt fonctionnel dans un environnement IDE, y compris une gestion fluide des erreurs de résolution de noms. Turbine traite près d'un million de lignes de code Java par seconde sur un seul thread, ce qui signifie que Metals peut recompiler l'ensemble de la base de code Java à intervalles réguliers. Cela permet de conserver presque tous les symboles inter-fichiers sur le classpath plutôt que sur le sourcepath, de sorte que la phase « analyze » de javac puisse s'exécuter au plus près de sa limite pratique pour une utilisation interactive : près de 100 000 lignes de code par seconde selon nos benchmarks.

Intégration du build : interroger 285 000 cibles Bazel avec les latences de l'éditeur

« Utile avant la synchronisation du build » ne signifie pas « ignorer le build ». Metals a toujours besoin de la fidélité du graphe de build pour de nombreuses fonctionnalités secondaires, notamment la navigation vers des dépendances tierces ou des sources générées, le respect des règles de shading personnalisées, la découverte de suites de tests et la configuration automatique des lanceurs de débogage. Chez Databricks, cela signifie rendre les métadonnées de 285 000 cibles JVM Bazel interrogeables avec les latences de l'éditeur.

Notre implémentation en production de cette couche est un serveur BSP interne écrit en Go et adapté à nos règles Bazel. Ce serveur BSP ne fait pas partie de cette version open-source, mais ses choix de conception méritent tout de même d'être transposés à d'autres implémentations BSP de Bazel.

Tout d'abord, Metals v2 n'invoque jamais Bazel via le serveur BSP à moins que l'utilisateur ne le demande explicitement. Dans un grand monorepo, la synchronisation de l'IDE en arrière-plan peut s'emparer du verrou Bazel et entrer en concurrence avec les builds lancés par les développeurs. La synchronisation du build est donc une action explicite de l'utilisateur plutôt qu'un comportement au démarrage ou une tâche de maintenance en arrière-plan. Les métadonnées obtenues sont stockées dans un instantané JSON dont la taille s'adapte grâce à la mutualisation des constantes pour les libellés, chemins et préfixes de dépôt répétés. Lorsque les utilisateurs se synchronisent, le serveur BSP ajoute progressivement de nouvelles cibles à cet instantané et fournit les métadonnées mises à jour via BSP.

Deuxièmement, il n'y a pas de format de configuration de synchronisation partagé. Les utilisateurs synchronisent des fichiers ou des répertoires individuels à la demande au fur et à mesure de leur édition pour améliorer la navigation ou la précision des diagnostics. D'après notre expérience, les ensembles de synchronisation prédéfinis s'étoffent avec le temps, sont copiés d'une équipe à l'autre et finissent par être plus lents que la synchronisation ciblée dont le développeur a réellement besoin.

Rassembler les trois couches

Ensemble, l'index sans build, les pipelines s'appuyant sur le compilateur et l'intégration du build axée sur les métadonnées offrent une intelligence de code à faible latence dans des bases de code Bazel de plusieurs millions de lignes. En le publiant en open-source sous licence Apache 2.0, nous voulons offrir à l'écosystème au sens large un choix qui n'existait pas auparavant : associer un agent de codage et un éditeur léger à une navigation Scala et Java riche, à une échelle où les serveurs de langage JVM existants n'offraient aucune solution crédible.

(Cet article de blog a été traduit à l'aide d'outils basés sur l'intelligence artificielle) Article original

Recevez les derniers articles dans votre boîte mail

Abonnez-vous à notre blog et recevez les derniers articles directement dans votre boîte mail.