Virtual Threads no Java: Quando Usar (e Quando NÃO Usar)
Voltar para o blog
Backend

Virtual Threads no Java: Quando Usar (e Quando NÃO Usar)

Virtual threads não são mágica: são uma ferramenta com casos de uso muito claros. Aprenda em profundidade quando eles resolvem seu problema de performance — e quando ativar vai deixar sua API mais lenta. Inclui como funciona por baixo dos panos, anti-padrões reais e um protocolo de teste antes de ir para produção.

5 min de leitura
3 views
27 de setembro de 2026
Compartilhe
Compartilhar:LinkedInXWhatsAppFacebook

Desde que os virtual threads chegaram estáveis, vi de tudo: times que resolveram gargalos reais com 3 linhas de configuração, e times que trocaram o pool de threads inteiro, pioraram a latência e culparam a JVM.

A verdade é simples: virtual threads resolvem UM problema específico — e resolvem muito bem. Fora dele, são ruído, ou pior: fonte de bugs sutis. Neste guia, você vai entender como eles funcionam por baixo dos panos, quando fazem sentido, quando não fazem, e como validar com números antes de ativar em produção.

O problema que eles resolvem

Virtual threads brilham quando sua aplicação passa a maior parte do tempo esperando: chamadas HTTP para outros serviços, queries no banco, leitura de arquivos, consumo de filas. I/O bloqueante, em resumo.

Para entender por que isso importa, você precisa entender o modelo antigo. Com threads de plataforma, cada requisição concorrente ocupa uma thread do SO. Uma thread de plataforma custa na ordem de 1MB de stack reservado, e o sistema operacional não gosta de lidar com milhares delas — o custo de troca de contexto e o agendamento viram gargalo.

O resultado prático que todo backend conhece: thread pool de 200 no Tomcat, requisições enfileiradas no pico, latência subindo em formato de degrau, e o time respondendo "vamos aumentar o pool" — o que só transfere a pressão para o banco de dados.

Com virtual threads, a JVM cria milhões de threads leves gerenciadas por ela mesma, não pelo SO. Quando uma thread virtual bloqueia em I/O, a JVM estaciona ela (desmonta seu stack da thread de plataforma, chamada carrier) e libera o carrier para executar outra tarefa. O custo de "esperar" praticamente desaparece.

Como funciona por baixo dos panos (versão prática)

Pense no modelo assim: virtual threads são tarefas leves que rodam em cima de um pool pequeno de threads de plataforma (os carriers). A JVM faz o agendamento em user-space, que é ordens de magnitude mais barato que o agendamento do kernel.

O ponto crítico é o conceito de pinning: quando uma thread virtual executa código que não pode ser estacionado — historicamente, blocos synchronized em execução quente, ou chamadas nativas (JNI) — ela fica "presa" ao seu carrier. Se muitas threads pinarem ao mesmo tempo, você esgota os carriers e o throughput desaba, às vezes ficando pior que o modelo antigo.

A partir do Java 24, a maior causa de pinning (synchronized) foi resolvida na implementação da JVM. Mas bibliotecas antigas no classpath, drivers JDBC defasados e código nativo ainda podem causar pinning. É por isso que "ativar a flag e esquecer" não é uma estratégia.

Como ativar no Spring Boot

Uma linha de propriedade:

spring.threads.virtual.enabled=true

Pronto. O Tomcat passa a rodar cada requisição em um virtual thread. Sem mudar seu código, sem programação reativa, sem WebFlux. O mesmo vale para o agendamento de tarefas (@Scheduled) e para os executores gerenciados pelo Boot.

Se quiser controle manual fora do Spring, o padrão é o executor de novo virtual thread por tarefa:

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { executor.submit(() -> chamarServicoExterno(id)); executor.submit(() -> chamarServicoExterno(outroId)); }

Repare no design: não existe pool. Cada tarefa ganha sua própria thread virtual, criada e descartada. Isso é intencional.

Quando NÃO usar

Aqui está a parte que ninguém conta nos tutoriais — e onde a maioria dos projetos se frustra.

1. Carga de CPU pesada. Virtual threads não criam poder de processamento. Se seu endpoint faz criptografia, processamento de imagem, geração de PDF ou cálculo intensivo, o gargalo é CPU — e você tem exatamente o mesmo número de núcleos que tinha antes. Milhões de threads virtuais disputando 8 núcleos só adicionam overhead de agendamento.

2. Pooling de virtual threads. Nunca faça pool de virtual threads. Eles são baratos de propósito — o design é criar e descartar. Envolver virtual threads em um ExecutorService com pool fixo é desperdiçar a feature e reintroduzir o gargalo que você tentou resolver. Se você sente "necessidade" de limitar concorrência, o limite deve estar no recurso compartilhado (connection pool do banco, rate limit do serviço externo), não nas threads.

3. ThreadLocal abusivo. Virtual threads incentivam o estilo thread-per-request. Se sua aplicação depende pesadamente de ThreadLocal para carregar contexto — tenant, usuário autenticado, dados de tracing — cada virtual thread é um contexto novo. Um cache em ThreadLocal que antes era reutilizado 200 vezes (tamanho do pool antigo) agora é recalculado milhões de vezes. O caminho certo em código moderno é Scoped Values, que foi desenhado exatamente para compartilhamento imutável de contexto.

4. Operações nativas e bibliotecas antigas. Chamadas JNI e drivers JDBC muito antigos podem pinar carriers. Antes de ativar em produção, verifique a versão do seu driver de banco e das bibliotecas de I/O do classpath.

5. Synchronized em hot path (versões antigas da JVM). Se você está em Java 21, blocos synchronized em código de alta concorrência ainda pinam. A regra prática nessa versão: prefira ReentrantLock em hot paths. No Java 24+, esse problema foi amplamente resolvido.

O protocolo de teste que vale mais que qualquer artigo

Antes de ativar em produção, meça. O protocolo que sigo:

Passo 1: Escolha o endpoint mais crítico sob carga — de preferência um que agregue dados de múltiplas fontes.

Passo 2: Rode um load test na configuração atual (k6, Gatling ou JMeter) e registre três números: throughput, p50 de latência e p99 de latência.

Passo 3: Ative virtual threads em ambiente idêntico ao de produção (mesmo hardware, mesmo connection pool) e repita o teste.

Passo 4: Compare. Se sua API é I/O bound, você provavelmente verá ganhos de 2 a 10x no throughput com p99 estável. Se é CPU bound, verá nada — ou uma leve piora pelo overhead de scheduling.

Passo 5: Monitore pinning. Nas versões atuais da JVM, eventos de pinning são reportados via JFR (Java Flight Recorder). Se os carriers estão constantemente presos, o ganho desaparece.

O cenário ideal na prática

Para concretizar: uma API REST que agrega dados de 3 microsserviços + 2 queries no banco por requisição, com 500 requisições simultâneas no pico. Esse é o caso de uso perfeito — muito I/O, pouco CPU, alta concorrência. Nesse perfil, virtual threads frequentemente eliminam a necessidade de migrar para programação reativa, que exige reescrever o stack inteiro e treinar o time.

O oposto também vale: se seu time já roda WebFlux com sucesso e o código é idiomático reativo, virtual threads não trazem ganho imediato — a reatividade já resolve o I/O. A migração de volta ao código imperativo é uma decisão de simplicidade de manutenção, não de performance.

Você já ativou virtual threads em produção? Comenta o resultado — ganho real ou decepção, os dois casos ensinam. Se quiser, no próximo post eu mostro como combinar virtual threads com Resilience4j para construir APIs resilientes de verdade.

nexucodeplay
Autor do artigo

nexucodeplay

Desenvolvedor Fullstack

Especialista em Java, Spring Boot, React, Flutter e arquitetura moderna de software.

Perguntas frequentes

Dúvidas comuns sobre o tema

Sim. Esse conteúdo aborda tecnologias e conceitos amplamente utilizados no mercado moderno de desenvolvimento.

Publicidade

Apoie o projeto acessando nossos parceiros.

Publicidade
Compartilhe

Curtiu esse conteúdo?

Compartilhe este artigo com outros desenvolvedores e ajude mais pessoas a evoluírem na carreira.

Continue estudando

Artigos relacionados

Conteúdos profissionais sobre arquitetura, backend moderno, frontend e performance.

Deixe seu comentário

Compartilhe sua opinião sobre este conteúdo.

Deixe um comentário

Comentários passam por moderação para evitar spam e manter a qualidade.

Comentários

Ainda não existem comentários.