Ir al contenido principal
Plataforma

Código abierto de Metals v2: el servidor de lenguaje Java y Scala de Databricks para bases de código con millones de líneas

Cómo cerrar la brecha de inteligencia de código hizo que Cursor funcionara para nuestro monorepo Bazel de 26 millones de líneas

por Ólafur Páll Geirsson, Iulian Dragos y Alessandro Patti

  • Metals v2 replantea la inteligencia de código para monorepos a gran escala que han superado los servidores de lenguaje JVM convencionales.
  • Metals ha sido el servidor de lenguaje oficial de Scala durante años. Con el soporte completo de Java en v2, empresas con algunas de las bases de código Java más grandes de la industria ahora están realizando pruebas piloto de Metals solo para Java.
  • La IA cambia el valor relativo de las características de Language Server Protocol (LSP). A medida que los ingenieros escriben menos código a mano, el bajo costo de configuración, la retroalimentación rápida y una orientación confiable en la base de código importan más que una amplia cobertura de autocompletado y refactorización.

La mayor parte del código en Databricks ahora es escrito por agentes. Para los momentos en que los ingenieros aún programan directamente, recurren a editores ligeros que se inician rápidamente y les permiten navegar por el código con una configuración mínima. Sin embargo, para Scala y Java, IntelliJ ha establecido el estándar durante años. A la escala de nuestro monorepo, era prácticamente el único editor capaz de mantener el ritmo. Esta publicación comparte cómo creamos una solución alternativa al extender Metals, el servidor de lenguaje de Scala ampliamente utilizado, para ofrecer un soporte de primer nivel para Java y escalar al tamaño de nuestro monorepo. En colaboración con el equipo upstream de Metals, ahora hemos publicado Metals v2 como código abierto para que cualquiera que tenga una base de código grande en Java y Scala pueda combinar su agente de programación con un editor ligero.

Metals v2 está disponible hoy en Cursor, VS Code y Neovim, con instrucciones de instalación disponibles en el sitio web de Metals.

Construyendo el efecto volante del IDE

En mayo de 2025, comenzamos a estandarizar el flujo de trabajo diario de edición de Databricks en torno a Cursor. Cursor y VS Code ya se usaban ampliamente en Databricks para el frontend y otros trabajos que no requerían JVM, y ambos contaban con un sólido soporte remoto SSH para nuestro entorno de desarrollo basado en la nube. Sin embargo, la mayoría de nuestros servicios están escritos en Scala y Java, y navegar por ellos a la escala de nuestro monorepo era el último obstáculo; el problema que nos propusimos resolver.

Estandarizar en un solo editor importaba más allá de las preferencias individuales. Una plataforma de IDE unificada crea un efecto volante: los equipos comparten una base común para la exploración y el desarrollo de código, mientras que el equipo de la plataforma puede concentrar las inversiones en un solo lugar. Cursor se convirtió en el editor principal para el trabajo con JVM en nuestro monorepo, y nos consolidamos lo suficiente como para no renovar la mayoría de nuestras licencias de IntelliJ este año.

image2.png
Figura 1: Una adopción más amplia aumenta el aprovechamiento de las inversiones en la plataforma, lo que mejora el IDE compartido y refuerza la adopción.

Tres señales muestran hasta qué punto llegó la transición a Cursor: el uso general del IDE, los eventos de apertura de archivos de Scala y Java donde Metals se encuentra en la ruta crítica, y la adopción fuera de Databricks.

Uso del IDE

La señal más amplia es el uso general del IDE. La adopción de Cursor creció por sí sola en Databricks, pero se estancó en septiembre de 2025, manteniéndose así durante meses. El crecimiento se reanudó con el lanzamiento de Metals v2 y, para julio de 2026, el 92% de los usuarios activos semanales de IDE abrían Cursor, en comparación con el 12% de IntelliJ. Entre los ingenieros que solo usan un único IDE, 2400 están ahora en Cursor, en comparación con los 120 de IntelliJ.

image3.png
Figura 2: Uso del IDE, abril de 2025 – julio de 2026. La línea negra marca el primer lanzamiento interno de Metals v2, que trasladó la navegación de Scala y Java a un sourcepath proporcionado por Metals (análisis profundo a continuación).

Eventos de apertura de archivos de Scala y Java

Una señal más precisa que el uso general del IDE es la proporción de archivos abiertos en cada IDE para Scala y Java, los lenguajes donde Metals v2 se encuentra en la ruta crítica. Desde que las primeras mejoras importantes de Metals v2 llegaron a Cursor en octubre de 2025, la participación de Cursor en los eventos de apertura de archivos de Scala y Java ha aumentado del 40% al 78%. La subida es más lenta que la adopción agregada del IDE porque Scala y Java son los lenguajes donde IntelliJ estaba más arraigado, pero la participación de Cursor sigue mostrando una tendencia alcista mes a mes.

image1.png
Figura 3: Eventos de apertura de archivos de Scala y Java por IDE, abril de 2025 – julio de 2026.

Adopción externa temprana

Metals v2 comenzó como un fork de Databricks, pero el objetivo siempre fue devolver el trabajo a la comunidad de código abierto. Junto con el equipo de Cursor y los mantenedores principales de Metals en VirtusLab, estamos preparando una versión estable de Metals v2 para reemplazar la versión estable actual v1.

Estamos contribuyendo a Metals v2 para que Cursor funcione bien en bases de código de Java con millones de líneas, centrándonos en mejorar el soporte de Bazel, la depuración y las pruebas.—Kevin Niparko, Cursor
La IA está cambiando la forma en que los desarrolladores usan los IDE, y Metals v2 avanza en la dirección correcta: inicio rápido, orientación confiable en la base de código y una arquitectura diseñada para grandes bases de código. Databricks validó este enfoque a una escala excepcional, y en VirtusLab estamos entusiasmados de ayudar a llevar este trabajo a la comunidad más amplia de Scala y JVM.—Krzysztof Romanowski, Director de Productividad de Desarrollo, VirtusLab

Las conversaciones con responsables de otras grandes bases de código de JVM han reforzado el mismo patrón que vimos en Databricks: demanda de Cursor, VS Code y Neovim con un sólido soporte remoto SSH, presión de la plataforma hacia herramientas unificadas y la falta de una alternativa viable con los servidores de lenguaje de JVM existentes a la escala de un monorepo.

Comenzamos a implementar Metals v2 en Stripe hace menos de un mes y, aun así, estamos constantemente impresionados con lo bien que funciona en nuestra base de código de Java, el entusiasmo de nuestros ingenieros y lo agradable que es trabajar y aprender de los mantenedores.—Mahib Hosain, Plataforma de Desarrolladores, Stripe

Este es el modelo que queremos para Metals v2: una infraestructura compartida para grandes bases de código de JVM, mantenida de forma abierta y moldeada por las empresas que necesitan que funcione a escala. El desarrollo continuo está liderado por VirtusLab; póngase en contacto con ellos para preguntas, comentarios o contribuciones a través del rastreador de problemas de GitHub o en metals@virtuslab.com.

Análisis profundo: cómo escala Metals v2

Todo lo anterior justifica el uso de Metals v2. El resto es para los lectores que deseen comprender mejor la ingeniería detrás de la inteligencia de código de baja latencia en un monorepo de 26 millones de líneas.

Dado que los agentes escriben la mayor parte del código, una orientación rápida en la base de código importaba más que una cobertura completa de autocompletado y refactorización del Language Server Protocol (LSP). Eso acotó el problema, pero no hizo que la solución fuera obvia: aún teníamos que ofrecer una navegación de baja latencia y fácil de configurar para un monorepo de Bazel en Scala y Java de este tamaño, y los LSP comerciales no estaban diseñados para soportar esa escala. Tuvimos que construir inteligencia de código a escala de repositorio desde los principios fundamentales y tratar la utilidad del inicio como una métrica clave que podíamos medir y mejorar.

Definición del tiempo hasta la inteligencia inicial (TTII)

El tiempo hasta la inteligencia inicial (TTII) mide qué tan rápido el editor se vuelve útil después de abrir el repositorio. Iniciamos el cronómetro cuando se activa el servidor de lenguaje y lo detenemos cuando las funciones más importantes están disponibles, asumiendo que no hay intervención del usuario. Estas funciones críticas incluyen la búsqueda difusa de símbolos en el espacio de trabajo, la opción de ir a la definición y la búsqueda de usos de símbolos en todo el repositorio.

Tres capas de Metals v2

Metals v2 es un servidor de lenguaje para Scala y Java. Comenzamos a partir de Metals v1, el servidor de lenguaje oficial de Scala, pero alcanzar nuestro objetivo de TTII requirió más que solo hacer algunos ajustes superficiales. Creamos un fork y rediseñamos tres capas centrales: el índice del repositorio, los pipelines de compilación de Scala y Java, y el límite de integración de compilación.

  1. Índice de repositorio sin compilación: Metals v2 elimina el Build Server Protocol (BSP) de la ruta crítica de inicio al indexar las fuentes del espacio de trabajo directamente utilizando su propio índice mbt, que se describe en la sección siguiente. La diferencia clave con respecto a Metals v1 es que Metals ahora posee el modelo de proyecto inicial en lugar de esperar a que el servidor de compilación lo proporcione.
  2. Pipelines interactivos respaldados por el compilador para Scala y Java: Metals v2 traslada la carga de símbolos de un classpath proporcionado por la compilación a un sourcepath proporcionado por Metals, lo que permite que los diagnósticos y la navegación reflejen con mayor precisión el código real en el disco en lugar de una instantánea desactualizada de la última compilación exitosa. Esto requirió replantear una larga lista de suposiciones fundamentales de la base de código v1.
  3. Integración de compilación basada primero en metadatos: Metals v2 sigue utilizando BSP, pero con un contrato más acotado. Metals v1 utilizaba el servidor de compilación en la ruta crítica del editor para los diagnósticos, mientras que Metals v2 traslada los diagnósticos rutinarios fuera del servidor de compilación y utiliza BSP principalmente para consultar metadatos de compilación: dependencias, fuentes generadas, detección de pruebas y lanzadores de depuración. Este cambio reduce la barrera para implementar un servidor BSP para Metals v2, y nuestro servidor BSP interno de Bazel valida que el modelo escala a grandes monorepos de Bazel.

Las siguientes secciones detallan cada capa a su vez.

El índice mbt: inteligencia en todo el repositorio antes de la sincronización de la compilación

mbt significa Metals Build Tool, y el índice mbt es el principal facilitador del TTII o del contrato de "utilidad inmediata". Es un índice direccionado por contenido de las fuentes del espacio de trabajo: Metals utiliza el comando git ls-files --stage para descubrir archivos y OIDs de blob de Git para decidir qué entradas del índice se pueden reutilizar.

Con la información de todo el repositorio disponible antes de la sincronización de la compilación, Metals puede responder a preguntas de la "primera milla":

  • diagnósticos para referencias entre archivos
  • búsqueda difusa de símbolos
  • ir a la definición en todo el repositorio, pero no en dependencias externas o código generado
  • búsquedas amplias de referencias e implementaciones mediante el uso creativo de filtros de Bloom por documento

El índice mbt es, en esencia, un mapa hash desde el archivo de origen a un resumen local del archivo. Cada entrada registra las declaraciones de paquetes del archivo, las definiciones con sus ubicaciones de origen y filtros de Bloom compactos para los identificadores a los que se hace referencia en el archivo. Las definiciones impulsan la búsqueda de símbolos en el espacio de trabajo y la función de ir a la definición (jump-to-definition), mientras que los filtros de Bloom permiten a Metals descartar rápidamente los archivos que no pueden contener una referencia antes de realizar comprobaciones más precisas. Dado que cada entrada se deriva de un solo archivo, las actualizaciones incrementales siguen siendo sencillas: cuando un archivo cambia, Metals vuelve a calcular la entrada de ese archivo y la reemplaza.

En nuestro monorrepositorio, el índice mbt persistido pesa 936MB sin comprimir y contiene información sobre 2.9m de símbolos en más de 142k archivos de Scala, Java y Protobuf. Una compilación de referencia limpia tarda 22 segundos con una utilización completa de la CPU en 32 núcleos, mientras que analizar un índice precompilado desde el disco tarda 5 segundos. En producción, medimos el TTII como el tiempo para iniciar el servidor, cargar un índice mbt desactualizado, actualizarlo con el estado más reciente de git ls-files --stage y reiniciar los compiladores de presentación de Scala y Java: p50 8.7s, p90 36.7s. La búsqueda difusa de símbolos en 2.9m de símbolos del espacio de trabajo es de p50 10ms, p90 95ms. Hay margen para reducir aún más el TTII, pero con estas cifras no es el cuello de botella que debemos abordar a continuación.

Pipeline de Scala: procesamiento de 24M de líneas de código en una sola instancia de compilador

El pipeline de Scala se basa en el compilador de presentación, un modo del verificador de tipos de Scala que almacena en caché y reutiliza la información de la tabla de símbolos en todas las ejecuciones de compilación. Esta reutilización, combinada con la resolución diferida de símbolos del compilador, permite que una sola instancia mantenga en alcance todo el código base de Scala de 24M de líneas mientras publica diagnósticos a p50 0.9s, p90 8.9s.

Esa única instancia del compilador de Scala se ejecuta en uno de dos modos, según la cantidad de información que Metals tenga del servidor de compilación sobre el archivo que se está editando. Antes de una sincronización de compilación, un compilador de respaldo adopta una postura permisiva, tratando cada archivo de origen en el repositorio como un candidato de dependencia elegible, lo que hace que la navegación sea útil de inmediato, incluso en código que aún no se compila en Bazel. Después de una sincronización de compilación, un compilador preciso se limita a los límites de classpath y sourcepath que informa el servidor de compilación, lo que hace que sus diagnósticos e información de dependencias sean precisos para la compilación. Ambos modos, el preciso y el de respaldo, se apoyan en las mismas dos técnicas para mantener manejable un sourcepath de este tamaño:

  • Modo de esquema para fuentes no abiertas. A las fuentes que no están abiertas en un editor se les eliminan los cuerpos de los métodos antes de la verificación de tipos. Esto conserva las firmas de tipo que el compilador necesita, al tiempo que evita el trabajo en los cuerpos de los métodos, que es donde se consume la mayor parte del tiempo de verificación de tipos.
  • Un índice de diseño de fuentes en memoria. Las rutas de origen de Scala no codifican de manera confiable la estructura de los paquetes, por lo que Metals ejecuta un analizador para compilar un índice ligero que asigna clases a ubicaciones de origen y, luego, carga los símbolos a través de ese índice en lugar de escanear el sistema de archivos. El compilador de respaldo crea este índice directamente a partir del índice mbt en menos de 50ms, reutilizando los datos de todo el repositorio que Metals ya tiene en lugar de volver a analizarlos desde el origen.

El modo preciso añade una tercera técnica: mantiene las fuentes de dependencias transitivas en el sourcepath, por lo que las ediciones en archivos de diferentes objetivos de Bazel se reflejan de inmediato en el editor sin tener que esperar a que Bazel genere un nuevo classpath, una limitación que frenaba a Metals v1.

Mantener un código base de 24M de líneas en una sola instancia de compilador de presentación está muy fuera de los límites para los que se diseñó originalmente el compilador de Scala. Metals v2 lo logra a través de dos modos de compilador basados en un conjunto compartido de técnicas para escalar el sourcepath. La función de ir a la definición (jump-to-definition), la característica más utilizada del editor, se ejecuta en todo el repositorio a p50 7ms, p90 575ms. Scala es el lenguaje en el que trabaja la mayoría de nuestros ingenieros, por lo que este pipeline era la pieza clave que debía funcionar para que Cursor se convirtiera en una alternativa real a IntelliJ en nuestro monorrepositorio.

Pipeline de Java: procesamiento de un millón de líneas de código Java por segundo con Turbine

Metals v2 implementa la superficie LSP de Java directamente en las API de javac. Evaluamos la posibilidad de reutilizar un servidor de lenguaje Java existente (implementaciones basadas en JDT y NetBeans), pero ambos se centran en la compilación de la misma manera que Metals v1, que es precisamente el acoplamiento que la versión v2 quería eliminar. En su lugar, compilar sobre javac permitió a Java compartir el sourcepath y el modelo de sincronización de compilación de Metals v2 con Scala, que sigue constituyendo la mayor parte de nuestro monorrepositorio, y la superficie de Java que necesitábamos era lo suficientemente pequeña como para mantenerla nosotros mismos.

Las API de javac proporcionaron suficiente acceso al compilador para implementar diagnósticos, navegación, resaltado sintáctico semántico y otros métodos clave de LSP, y funcionaron bien con código parcialmente roto, lo cual es importante para el flujo de trabajo de un editor interactivo. Al igual que con Scala, mantuvimos deliberadamente un alcance más acotado para las funciones de edición activa, como las refactorizaciones y el autocompletado.

El principal problema de escalabilidad con esta arquitectura apareció en archivos que provocaban un rendimiento patológico en la fase "enter" de javac al importar de forma transitiva millones de líneas de código en la capa de esquema de símbolos. Metals v2 soluciona esto con un modo javaSymbolLoader: "turbine-classpath", habilitado de forma predeterminada, que utiliza una versión modificada del compilador de cabeceras Turbine para producir un classpath de repositorio que funcione en un entorno de IDE, incluido el manejo adecuado de errores de resolución de nombres. Turbine procesa cerca de un millón de líneas de código Java por segundo en un solo hilo, lo que significa que Metals puede recompilar todo el código base de Java a intervalos regulares. Esto mantiene casi todos los símbolos entre archivos en el classpath en lugar del sourcepath, por lo que la fase "analyze" de javac puede ejecutarse cerca de su límite práctico para uso interactivo: casi 100k líneas de código por segundo según nuestras pruebas de rendimiento.

Integración de compilación: consulta de 285k objetivos de Bazel con latencias de editor

«Útil antes de la sincronización de compilación» no significa «ignorar la compilación». Metals sigue necesitando fidelidad en el gráfico de compilación para una larga lista de funciones, incluida la navegación a dependencias de terceros o fuentes generadas, el respeto de las reglas de sombreado (shading) personalizadas, el descubrimiento de conjuntos de pruebas y la autoconfiguración de lanzadores de depuración. En Databricks, eso significa hacer que los metadatos de 285k objetivos de JVM de Bazel se puedan consultar con latencias de editor.

Nuestra implementación de producción de esta capa es un servidor BSP interno escrito en Go y adaptado a nuestras reglas de Bazel. Este servidor BSP no forma parte de este lanzamiento de código abierto, pero vale la pena trasladar sus decisiones de diseño a otras implementaciones de BSP de Bazel.

En primer lugar, Metals v2 nunca invoca a Bazel a través del servidor BSP a menos que el usuario lo solicite explícitamente. En un monorrepositorio grande, la sincronización en segundo plano del IDE puede bloquear a Bazel y competir con las compilaciones iniciadas por el desarrollador, por lo que la sincronización de compilación es una acción explícita del usuario en lugar de un comportamiento de inicio o una tarea de mantenimiento en segundo plano. Los metadatos resultantes se almacenan en una instantánea JSON que se escala mediante la agrupación de constantes para etiquetas, rutas y prefijos de repositorio repetidos. Cuando los usuarios realizan la sincronización, el servidor BSP añade de forma incremental más objetivos a esta instantánea y ofrece los metadatos actualizados a través de BSP.

En segundo lugar, no existe un formato de configuración de sincronización compartido. Los usuarios sincronizan archivos o directorios individuales bajo demanda a medida que editan para mejorar la navegación o la fidelidad de los diagnósticos. Según nuestra experiencia, los conjuntos de sincronización predefinidos crecen con el tiempo, se copian entre equipos y se vuelven más lentos que la sincronización enfocada que el desarrollador realmente necesita.

Uniendo las tres capas

Juntos, el índice libre de compilación, los pipelines respaldados por el compilador y la integración de compilación centrada en metadatos ofrecen inteligencia de código de baja latencia en códigos base de Bazel de millones de líneas. Al publicarlo como código abierto bajo la licencia Apache 2.0, queremos ofrecer al ecosistema en general una opción que antes no existía: emparejar un agente de programación y un editor ligero con una navegación enriquecida en Scala y Java, a una escala en la que los servidores de lenguaje JVM existentes no ofrecían un camino viable.

(Esta entrada del blog ha sido traducida utilizando herramientas basadas en inteligencia artificial) Publicación original

Recibe las últimas publicaciones en tu bandeja de entrada

Suscríbete a nuestro blog y recibe las últimas publicaciones directamente en tu bandeja de entrada.