Ir para o conteúdo principal
Plataforma

Código aberto do Metals v2: o servidor de linguagem Java e Scala do Databricks para bases de código com milhões de linhas

Como reduzir a lacuna de inteligência de código fez o Cursor funcionar em nosso monorepo Bazel de 26 milhões de linhas

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

  • O Metals v2 repensa a inteligência de código para monorepos de grande escala que superaram os servidores de linguagem JVM convencionais.
  • O Metals tem sido o servidor de linguagem Scala oficial há anos. Com suporte completo a Java no v2, empresas com algumas das maiores bases de código Java do setor agora estão testando o Metals apenas para Java.
  • A AI altera o valor relativo dos recursos do Language Server Protocol (LSP). À medida que os engenheiros escrevem menos código manualmente, o baixo custo de configuração, o feedback rápido e a orientação confiável da base de código importam mais do que uma ampla cobertura de preenchimento automático e refatoração.

A maior parte do código na Databricks agora é escrita por agentes. Para os momentos em que os engenheiros ainda colocam a mão na massa, eles recorrem a editores leves que iniciam rapidamente e permitem navegar pelo código com pouca configuração. Para Scala e Java, no entanto, o IntelliJ definiu o padrão por anos. Na escala do nosso monorepo, ele era praticamente o único editor que conseguia acompanhar o ritmo. Este post compartilha como criamos uma solução alternativa estendendo o Metals, o servidor de linguagem Scala amplamente utilizado, para ter suporte de primeira classe para Java e escalar para o tamanho do nosso monorepo. Em colaboração com a equipe upstream do Metals, agora disponibilizamos o Metals v2 em código aberto para que qualquer pessoa com uma grande base de código Java e Scala possa parear seu agente de codificação com um editor leve.

O Metals v2 está disponível hoje no Cursor, VS Code e Neovim, com instruções de instalação disponíveis no site do Metals.

Construindo o efeito volante do IDE

Em maio de 2025, começamos a padronizar o fluxo de trabalho diário de edição da Databricks em torno do Cursor. O Cursor e o VS Code já eram amplamente utilizados na Databricks para frontend e outros trabalhos não JVM, e ambos tinham forte suporte remoto via SSH para nosso ambiente de desenvolvimento baseado em nuvem. No entanto, a maioria dos nossos serviços é escrita em Scala e Java, e navegar por eles na escala do nosso monorepo era o grande obstáculo — o problema que nos propusemos a resolver.

Padronizar em um único editor importava mais do que as preferências individuais. Uma plataforma de IDE unificada cria um efeito volante: as equipes compartilham uma mesma base para exploração e desenvolvimento de código, enquanto a equipe da plataforma pode concentrar os investimentos em um só lugar. O Cursor tornou-se o editor principal para o trabalho em JVM no nosso monorepo, e nos consolidamos tanto que não renovamos a maioria das nossas licenças do IntelliJ este ano.

image2.png
Figura 1: Uma adoção mais ampla aumenta a alavancagem dos investimentos na plataforma, o que melhora a IDE compartilhada e reforça a adoção.

Três sinais mostram quão longe foi a mudança para o Cursor: o uso geral de IDEs, eventos de abertura de arquivos Scala e Java onde o Metals está no caminho crítico, e a adoção fora da Databricks.

Uso de IDE

O sinal mais amplo é o uso geral de IDEs. A adoção do Cursor cresceu por conta própria na Databricks, mas estagnou em setembro de 2025, mantendo-se estável por meses. O crescimento foi retomado com o lançamento do Metals v2 e, em julho de 2026, 92% dos usuários ativos semanais de IDE abriam o Cursor, em comparação com 12% do IntelliJ. Entre engenheiros que usam apenas uma única IDE, 2,4 mil estão agora no Cursor, em comparação com 120 no IntelliJ.

image3.png
Figura 2: Uso de IDE, abril de 2025 – julho de 2026. A linha preta marca o primeiro lançamento interno do Metals v2, que moveu a navegação de Scala e Java para um sourcepath fornecido pelo Metals (análise profunda abaixo).

Eventos de abertura de arquivos Scala e Java

Um sinal mais detalhado do que o uso geral de IDEs é a proporção de arquivos abertos em cada IDE para Scala e Java, as linguagens onde o Metals v2 está no caminho crítico. Desde que as primeiras grandes melhorias do Metals v2 chegaram ao Cursor em outubro de 2025, a participação do Cursor nos eventos de abertura de arquivos Scala e Java subiu de 40% para 78%. A subida é mais lenta do que a adoção agregada de IDEs porque Scala e Java eram onde o IntelliJ estava mais consolidado, mas a participação do Cursor continua em tendência de alta mês a mês.

image1.png
Figura 3: Eventos de abertura de arquivos Scala e Java por IDE, abril de 2025 – julho de 2026.

Adoção externa inicial

O Metals v2 começou como um fork da Databricks, mas o objetivo sempre foi trazer o trabalho de volta para a comunidade de código aberto. Junto com a equipe do Cursor e os mantenedores principais do Metals na VirtusLab, estamos preparando uma versão estável do Metals v2 para substituir a atual versão estável v1.

Estamos contribuindo para o Metals v2 para fazer o Cursor funcionar bem em bases de código Java com milhões de linhas, com foco na melhoria do suporte ao Bazel, depuração e testes.—Kevin Niparko, Cursor
A IA está mudando a forma como os desenvolvedores usam IDEs, e o Metals v2 avança na direção certa: inicialização rápida, orientação confiável da base de código e uma arquitetura construída para grandes bases de código. A Databricks validou essa abordagem em uma escala excepcional, e a VirtusLab está entusiasmada em ajudar a trazer este trabalho para a comunidade mais ampla de Scala e JVM.—Krzysztof Romanowski, Head of Development Productivity, VirtusLab

Conversas com outras grandes bases de código JVM reforçaram o mesmo padrão que vimos na Databricks: demanda por Cursor, VS Code e Neovim com forte suporte remoto via SSH, pressão da plataforma por ferramentas unificadas e nenhum caminho viável usando os servidores de linguagem JVM existentes na escala de monorepo.

Começamos a implementar o Metals v2 na Stripe há menos de um mês, mas estamos constantemente impressionados com o quão bem ele funciona em nossa base de código Java, com o entusiasmo de nossos engenheiros e com o quão prazeroso é trabalhar e aprender com os mantenedores.—Mahib Hosain, Developer Platform, Stripe

Este é o modelo que queremos para o Metals v2: infraestrutura compartilhada para grandes bases de código JVM, mantida de forma aberta e moldada pelas empresas que precisam que ela funcione em escala. O desenvolvimento contínuo é liderado pela VirtusLab. Entre em contato com eles para dúvidas, feedback ou contribuições por meio do rastreador de problemas do GitHub ou pelo e-mail metals@virtuslab.com.

Análise profunda: como o Metals v2 escala

Tudo o que foi apresentado acima justifica o Metals v2. O restante é para leitores que desejam entender melhor a engenharia por trás da inteligência de código de baixa latência em um monorepo de 26 milhões de linhas.

Como os agentes escrevem a maior parte do código, a orientação rápida na base de código importava mais do que a cobertura abrangente de autocompletar e refatoração do LSP (Language Server Protocol). Isso estreitou o problema, mas não tornou a solução óbvia: ainda precisávamos fornecer uma navegação de fácil configuração e baixa latência para um monorepo Bazel em Scala e Java desse tamanho, e os LSPs prontos para uso não foram projetados para suportar essa escala. Tivemos que construir inteligência de código em escala de repositório a partir de princípios fundamentais e tratar a utilidade na inicialização como uma métrica essencial que pudéssemos medir e melhorar.

Definindo o tempo para inteligência inicial (TTII)

O tempo para inteligência inicial (TTII) mede a rapidez com que o editor se torna útil após a abertura do repositório. Iniciamos o cronômetro quando o servidor de linguagem é ativado e o paramos quando os recursos mais importantes estão disponíveis, assumindo que não haja intervenção do usuário. Esses recursos críticos incluem a busca aproximada (fuzzy search) de símbolos no espaço de trabalho, ir para a definição (jump-to-definition) e encontrar usos de símbolos em todo o repositório.

Três camadas do Metals v2

O Metals v2 é um servidor de linguagem para Scala e Java. Começamos a partir do Metals v1, o servidor de linguagem oficial do Scala, mas atingir nossa meta de TTII exigiu mais do que apenas pequenos ajustes. Fizemos um fork dele e reformulamos três camadas centrais: o índice do repositório, os pipelines do compilador Scala e Java e o limite de integração do build.

  1. Índice de repositório livre de build: O Metals v2 remove o Build Server Protocol (BSP) do caminho crítico de inicialização, indexando as fontes do espaço de trabalho diretamente usando seu próprio índice mbt descrito na seção abaixo. A principal diferença em relação ao Metals v1 é que o Metals agora possui o modelo de projeto inicial em vez de esperar que o servidor de build o forneça.
  2. Pipelines interativos baseados em compilador para Scala e Java: O Metals v2 move o carregamento de símbolos de um classpath fornecido pelo build para um sourcepath fornecido pelo Metals, permitindo que diagnósticos e navegação reflitam com maior precisão o código real no disco, em vez de um snapshot desatualizado da última compilação bem-sucedida. Isso exigiu repensar uma longa lista de premissas fundamentais da base de código v1.
  3. Integração de build focada em metadados: O Metals v2 ainda usa BSP, mas com um contrato mais restrito. O Metals v1 usava o servidor de build no caminho crítico do editor para diagnósticos, enquanto o Metals v2 move os diagnósticos de rotina para fora do servidor de build e usa o BSP principalmente para consultar metadados de build: dependências, fontes geradas, descoberta de testes e inicializadores de depuração. Essa mudança reduz a barreira para implementar um servidor BSP para o Metals v2, e nosso servidor BSP Bazel interno valida que o modelo escala para grandes monorepos Bazel.

As seções abaixo detalham cada camada por vez.

O índice mbt: inteligência em todo o repositório antes da sincronização do build

mbt significa Metals Build Tool, e o índice mbt é o principal viabilizador do TTII ou do contrato de "utilidade imediata". Trata-se de um índice endereçado por conteúdo das fontes do espaço de trabalho: o Metals usa o comando git ls-files --stage para descobrir arquivos e OIDs de blob do Git para decidir quais entradas do índice podem ser reutilizadas.

Com informações de todo o repositório disponíveis antes da sincronização do build, o Metals pode responder a perguntas iniciais:

  • diagnósticos para referências entre arquivos
  • busca aproximada de símbolos
  • ir para a definição em todo o repositório, exceto para dependências externas ou código gerado
  • busca ampla de referências e implementações por meio do uso criativo de filtros de Bloom por documento

O índice mbt é, na prática, um hash map do arquivo de origem para um resumo local do arquivo. Uma entrada registra as declarações de pacote do arquivo, definições com locais de origem e filtros de Bloom compactos para identificadores referenciados no arquivo. As definições alimentam a busca de símbolos no espaço de trabalho (workspace) e o recurso de ir para a definição (jump-to-definition), enquanto os filtros de Bloom permitem que o Metals descarte rapidamente arquivos que não podem conter uma referência antes de realizar verificações mais precisas. Como cada entrada é derivada de apenas um arquivo, as atualizações incrementais continuam simples: quando um arquivo é alterado, o Metals recalcula a entrada desse arquivo e a substitui.

Em nosso monorepo, o índice mbt persistido pesa 936 MB descompactado e contém informações sobre 2,9 milhões de símbolos em mais de 142 mil arquivos Scala, Java e Protobuf. Uma compilação de benchmark limpa leva 22 segundos com utilização total de CPU em 32 núcleos, enquanto a análise de um índice pré-compilado a partir do disco leva 5 segundos. Em produção, medimos o TTII como o tempo para iniciar o servidor, carregar um índice mbt desatualizado, atualizá-lo em relação ao estado mais recente do git ls-files --stage e reiniciar os compiladores de apresentação do Scala e do Java: p50 8,7s, p90 36,7s. A busca aproximada (fuzzy) de símbolos em 2,9 milhões de símbolos do espaço de trabalho é de p50 10ms, p90 95ms. Há espaço para reduzir ainda mais o TTII, mas, com esses números, ele não é o gargalo que precisamos resolver agora.

Scala pipeline: processando 24 milhões de linhas de código em uma única instância de compilador

O pipeline do Scala é construído em torno do compilador de apresentação (presentation compiler), um modo do verificador de tipos (type-checker) do Scala que armazena em cache e reutiliza informações da tabela de símbolos em todas as execuções de compilação. Essa reutilização, combinada com a resolução tardia (lazy) de símbolos do compilador, permite que uma única instância mantenha toda a base de código Scala de 24 milhões de linhas no escopo, enquanto publica diagnósticos em p50 0,9s, p90 8,9s.

Essa única instância do compilador Scala funciona em um de dois modos, dependendo de quanta informação o Metals possui do servidor de compilação sobre o arquivo que está sendo editado. Antes de uma sincronização de compilação (build sync), um compilador de contingência (fallback compiler) adota uma visão permissiva, tratando cada arquivo de origem no repositório como um candidato a dependência elegível, tornando a navegação útil imediatamente, mesmo em códigos que ainda não são compilados no Bazel. Após uma sincronização de compilação, um compilador preciso (precise compiler) se restringe aos limites de classpath e sourcepath relatados pelo servidor de compilação, o que torna seus diagnósticos e informações de dependência precisos em relação à compilação. Tanto o modo preciso quanto o de contingência se apoiam nas mesmas duas técnicas para manter um sourcepath desse tamanho gerenciável:

  • Modo de contorno (outline mode) para origens não abertas. Arquivos de origem que não estão abertos em um editor têm seus corpos de método removidos antes da verificação de tipo. Isso preserva as assinaturas de tipo de que o compilador precisa, evitando o trabalho nos corpos de método, onde a maior parte do tempo de verificação de tipo é gasta.
  • Um índice de layout de origem em memória. Os caminhos de origem do Scala não codificam a estrutura do pacote de forma confiável, por isso o Metals executa um analisador (parser) para criar um índice leve que mapeia classes para locais de origem e, em seguida, carrega símbolos por meio desse índice em vez de escanear o sistema de arquivos. O compilador de contingência cria esse índice diretamente a partir do índice mbt em menos de 50ms, reutilizando os dados de todo o repositório que o Metals já possui, em vez de analisar novamente a partir da origem.

O modo preciso adiciona uma terceira técnica: ele mantém as origens de dependência transitiva no sourcepath, de modo que as edições em arquivos de diferentes alvos do Bazel sejam refletidas imediatamente no editor, sem esperar que o Bazel produza um novo classpath, uma limitação que atrasava o Metals v1.

Manter uma base de código de 24 milhões de linhas em uma única instância do compilador de apresentação está muito além do que o compilador Scala foi originalmente projetado para fazer. O Metals v2 consegue isso por meio de dois modos de compilador baseados em um conjunto compartilhado de técnicas para dimensionar o sourcepath. O recurso de ir para a definição (jump-to-definition), a funcionalidade mais usada do editor, é executado em todo o repositório em p50 7ms, p90 575ms. O Scala é a linguagem na qual a maioria dos nossos engenheiros trabalha, por isso esse pipeline era a peça que precisava funcionar para que o Cursor se tornasse uma alternativa real ao IntelliJ em nosso monorepo.

Pipeline do Java: processando um milhão de linhas de código Java por segundo com o Turbine

O Metals v2 implementa a superfície LSP do Java diretamente nas APIs do javac. Avaliamos a reutilização de um servidor de linguagem Java existente (implementações baseadas em JDT e NetBeans), mas ambos são centrados na compilação da mesma forma que o Metals v1 era, exatamente o acoplamento que a versão v2 buscou remover. Em vez disso, construir com base no javac permitiu que o Java compartilhasse o sourcepath e o modelo de sincronização de compilação do Metals v2 com o Scala, que ainda compõe a maior parte do nosso monorepo, e a superfície Java de que precisávamos era pequena o suficiente para que pudéssemos mantê-la nós mesmos.

As APIs do javac forneceram acesso suficiente ao compilador para implementar diagnósticos, navegação, realce semântico (semantic highlighting) e outros métodos LSP importantes, e se comportaram bem em códigos parcialmente corrompidos, o que é importante para um fluxo de trabalho de edição interativo. Assim como no Scala, mantivemos deliberadamente os recursos de edição ativa, como refatorações e preenchimentos automáticos (completions), com um escopo mais restrito.

O principal problema de escalabilidade com essa arquitetura surgiu em arquivos que causavam um desempenho patológico na fase "enter" do javac ao importar de forma transitiva milhões de linhas de código na camada de contorno de símbolos. O Metals v2 resolve isso com um modo javaSymbolLoader: "turbine-classpath", ativado por padrão, que usa uma versão modificada do compilador de cabeçalho Turbine para produzir um classpath de repositório que funciona em um ambiente de IDE, incluindo o tratamento adequado de erros de resolução de nomes. O Turbine processa cerca de um milhão de linhas de código Java por segundo em uma única thread, o que significa que o Metals pode recompilar toda a base de código Java em intervalos regulares. Isso mantém quase todos os símbolos entre arquivos no classpath em vez do sourcepath, permitindo que a fase "analyze" do javac seja executada perto de seu limite prático para uso interativo: quase 100 mil linhas de código por segundo em nossos benchmarks.

Integração de compilação: consultando 285 mil alvos do Bazel com latências de editor

“Útil antes da sincronização de compilação” não significa “ignorar a compilação”. O Metals ainda precisa de fidelidade ao gráfico de compilação (build-graph) para uma longa lista de recursos, incluindo navegação para dependências de terceiros ou origens geradas, respeito a regras de sombreamento (shading) personalizadas, descoberta de suítes de testes e autoconfiguração de inicializadores de depuração. Na Databricks, isso significa tornar os metadados de 285 mil alvos JVM do Bazel consultáveis com latências de editor.

Nossa implementação de produção dessa camada é um servidor BSP interno escrito em Go e adaptado às nossas regras do Bazel. Esse servidor BSP não faz parte desta versão de código aberto, mas suas escolhas de design ainda valem a pena ser aplicadas a outras implementações de BSP do Bazel.

Primeiro, o Metals v2 nunca invoca o Bazel por meio do servidor BSP, a menos que o usuário solicite explicitamente. Em um monorepo grande, a sincronização em segundo plano da IDE pode bloquear o Bazel e competir com as compilações iniciadas pelo desenvolvedor. Por isso, a sincronização de compilação é uma ação explícita do usuário, e não um comportamento de inicialização ou uma tarefa de manutenção em segundo plano. Os metadados resultantes são armazenados em um snapshot JSON que se expande por meio de agrupamento de constantes (constant pooling) para rótulos, caminhos e prefixos de repositório repetidos. Quando os usuários sincronizam, o servidor BSP adiciona incrementalmente mais alvos a esse snapshot e fornece os metadados atualizados via BSP.

Segundo, não há um formato de configuração de sincronização compartilhado. Os usuários sincronizam arquivos ou diretórios individuais sob demanda enquanto editam para melhorar a navegação ou a fidelidade do diagnóstico. Em nossa experiência, os conjuntos de sincronização predefinidos crescem com o tempo, são copiados entre equipes e se tornam mais lentos do que a sincronização focada de que o desenvolvedor realmente precisa.

Unindo as três camadas

Juntos, o índice sem compilação (build-free), os pipelines baseados em compilador e a integração de compilação focada em metadados oferecem inteligência de código de baixa latência em bases de código do Bazel com milhões de linhas. Ao disponibilizá-lo como código aberto sob a licença Apache 2.0, queremos dar ao ecossistema mais amplo uma escolha que não existia antes: combinar um assistente de programação (coding agent) e um editor leve com uma navegação rica em Scala e Java, em uma escala na qual os servidores de linguagem JVM existentes não ofereciam um caminho viável.

(Esta publicação no blog foi traduzida utilizando ferramentas baseadas em inteligência artificial) Publicação original

Receba os posts mais recentes na sua caixa de entrada

Assine nosso blog e receba os posts mais recentes diretamente na sua caixa de entrada.