Se trabalha com Java, provavelmente já ouviu falar do Virtual Threads. Lançadas oficialmente no Java 21, elas não são apenas uma “atualizaçãozinha” no código; elas representam uma mudança de paradigma na forma como pensamos em escalabilidade e concorrência.
Neste artigo, vamos traduzir a complexidade técnica para entender por que elas são o “santo graal” para quem precisa de sistemas rápidos, eficientes e, acima de tudo, fáceis de manter.
1. O Problema: O “Peso” das Threads Antigas
Até há pouco tempo, cada thread no Java era uma Thread de Plataforma. Imagine que cada thread é um funcionário especializado que exige um escritório próprio e caro (cerca de 1MB de memória de reserva). Se recebesse 2.000 pedidos ao mesmo tempo, precisaria de 2.000 escritórios. Rapidamente, o prédio (o seu servidor) ficaria sem espaço, mesmo que os funcionários estivessem apenas à espera que um e-mail (ou uma resposta da base de dados) chegasse.
Isto forçava os desenvolvedores a usar a “programação reativa” (como WebFlux ou RxJava) — uma técnica poderosa, mas extremamente complexa de ler, escrever e debugar. Era como se, para poupar espaço no prédio, tivéssemos de aprender uma língua nova e difícil.
2. A Solução: O que são as Virtual Threads?
As Virtual Threads são threads “peso-pena”. Em vez de 1MB, elas ocupam apenas alguns kilobytes de memória inicial. Elas não são geridas diretamente pelo sistema operativo, mas sim pela própria Máquina Virtual Java (JVM).
A grande diferença:
- Threads de Plataforma: Consegue criar milhares (o limite é o SO).
- Virtual Threads: Consegue criar milhões no mesmo hardware comum.
Elas permitem que volte ao modelo simples de “uma thread por requisição”, sem o medo de deitar o servidor abaixo.
3. Os Bastidores: Carrier Threads e o “Agendamento”
Para entender a “magia”, precisamos de conhecer as Carrier Threads (Threads Transportadoras).
Imagine que as Virtual Threads são passageiros e as Threads de Plataforma são os assentos de um avião.
- Uma Virtual Thread precisa de executar código.
- A JVM “monta” essa Virtual Thread num assento (uma Carrier Thread).
- Assim que a Virtual Thread precisa de esperar por algo (I/O), ela levanta-se do assento.
- O assento fica livre imediatamente para outro passageiro (outra Virtual Thread) trabalhar.
Este processo de “montar” e “desmontar” é extremamente rápido e invisível para si.
4. Como a Mágica Acontece na Prática?
O segredo está no bloqueio. No modelo antigo, se um cliente estivesse a decidir o prato (operação de I/O), o garçom (a CPU) ficava parado à frente dele, bloqueado e inútil para os outros.
Com as Virtual Threads, no momento em que o código faz uma chamada de rede ou consulta ao banco de dados, o Java deteta que haverá uma espera. Ele suspende a Virtual Thread, liberta a CPU para outras tarefas e, quando a resposta chega, a thread é retomada exatamente onde parou. O “garçom” nunca fica parado.
Exemplo de código: Como criar uma?
É tão simples quanto isto:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
System.out.println("Olá de uma Virtual Thread!");
// Simula uma espera de rede
Thread.sleep(Duration.ofSeconds(1));
return "Sucesso";
});
}
5. Onde usar (e onde NÃO usar)
Não tente usar Virtual Threads para tudo. Existe um cenário onde elas brilham e outro onde são irrelevantes:
- ✅ Use em tarefas de I/O (I/O-Bound): Chamadas de API, consultas a bases de dados, leitura de ficheiros e microserviços. É aqui que o ganho de escalabilidade é massivo.
- ❌ Evite em tarefas de CPU (CPU-Bound): Cálculos matemáticos complexos, processamento de vídeo ou criptografia pesada. Nesses casos, a CPU é o gargalo real; criar mais threads não vai acelerar o cálculo, só vai criar confusão na gestão.
6. Atenção às “Ciladas” no Caminho
Mesmo sendo fantásticas, as Virtual Threads têm algumas particularidades que podem comprometer a performance se ignoradas:
O Problema do “Pinning” (Fixação)
Até versões anteriores ao Java 24, se usasse a palavra-chave synchronized ou chamasse código nativo (JNI) dentro de uma Virtual Thread, ela ficava “colada” à Carrier Thread e não conseguia ser desmontada durante a espera.
- Dica: Atualize para o Java 24 (onde isto foi muito melhorado) ou substitua
synchronizedporReentrantLock.

Esqueça os Pools de Threads
Estamos habituados a criar pools (como o FixedThreadPool) para não esgotar recursos. Com Virtual Threads, a regra muda: não faça pooling. Elas foram feitas para serem descartáveis. Se precisar de limitar o acesso a um recurso (como uma base de dados que só aguenta 10 conexões), use um Semaphore, não limite o número de threads.
Cuidado com o ThreadLocal e os Scoped Values
Ter milhões de threads significa que, se cada uma guardar um objeto pesado no ThreadLocal, a sua memória RAM vai desaparecer num instante. O Java introduziu os Scoped Values (finalizados no Java 25) como uma alternativa muito mais leve e segura para partilhar dados entre tarefas.

7. O Ecossistema já está pronto!
A melhor notícia é que não precisa de implementar tudo do zero. As principais ferramentas já suportam Virtual Threads:
- Spring Boot 3.2+: Basta uma propriedade no
application.properties(spring.threads.virtual.enabled=true) para que o Tomcat passe a usar Virtual Threads. - Quarkus, Micronaut e Hibernate: Todos já possuem integrações nativas para tirar proveito desta velocidade.
8. Conclusão: O Futuro é Simples
As Virtual Threads trazem o melhor de dois mundos: a escalabilidade massiva que antes só se conseguia com código reativo complexo, mas com a simplicidade do código sequencial que qualquer programador Java sabe escrever e debugar.
Se quer preparar a sua aplicação para o próximo nível:
- Migre para o Java 21 (LTS) ou superior.
- Identifique os seus maiores tempos de espera (I/O).
- Ative as Virtual Threads nas suas frameworks e deixe a JVM fazer o trabalho pesado de gestão de recursos.
O Java nunca foi tão ágil, moderno e eficiente!