Capa: Virtual Threads no Spring Boot 3.2 e Java 21
A migração para o Java 21 e Spring Boot 3.2 traz as Virtual Threads para solucionar gargalos de I/O bloqueante.

Introdução: O Fim do Gargalo de I/O no Desenvolvimento Java

Durante anos, desenvolvedores que utilizavam o ecossistema Spring enfrentaram um desafio estrutural conhecido: o modelo de concorrência baseado em uma thread por requisição (thread-per-request). Sob este paradigma tradicional, cada conexão de entrada consome uma thread dedicada do sistema operacional. Quando essa thread precisa realizar uma operação de I/O bloqueante — como uma consulta ao banco de dados, uma chamada de API externa ou leitura de arquivos —, ela permanece inativa, aguardando a resposta, mas ainda consumindo memória e recursos do processador.

Com o lançamento do Java 21 e a integração nativa no Spring Boot 3.2, esse cenário mudou drasticamente através da introdução das Virtual Threads (fruto do Projeto Loom). Esta tecnologia redefine a forma como lidamos com concorrência, permitindo que aplicações processem milhões de requisições simultâneas sem a sobrecarga de memória das threads tradicionais da plataforma.

Este artigo técnico detalha o processo de migração, os conceitos fundamentais do Projeto Loom, as configurações necessárias no Spring Boot 3.2, a conteinerização com Docker e como validar essa arquitetura por meio de testes de carga práticos.

O que é o Projeto Loom e as Virtual Threads do Java 21?

O Projeto Loom é uma iniciativa da OpenJDK que visa reduzir drasticamente o esforço de escrever, manter e depurar aplicações concorrentes de alto rendimento. A principal entrega desse projeto são as Virtual Threads, disponíveis como um recurso de produção a partir do Java 21.

Diferente das threads clássicas da plataforma (Platform Threads), que são mapeadas diretamente pelo sistema operacional (1:1), as Virtual Threads são threads leves gerenciadas diretamente pela Java Virtual Machine (JVM). Elas operam em um modelo M:N, onde milhares ou até milhões de threads virtuais podem ser executadas sobre um pool reduzido de threads da plataforma (conhecidas como Carrier Threads).

Como funciona o desbloqueio de I/O?

Quando uma operação de I/O bloqueante é invocada dentro de uma Virtual Thread, a JVM intercepta essa chamada. Em vez de bloquear a Carrier Thread subjacente, a Virtual Thread ativa é colocada em espera (yield) e desassociada da Carrier Thread. Isso libera a thread da plataforma imediatamente para executar outras Virtual Threads. Assim que a operação de I/O é concluída, a Virtual Thread em espera é reagendada para continuar sua execução a partir do ponto em que parou.

Essa arquitetura permite que aplicações desenvolvidas no estilo imperativo e síncrono clássico — muito mais simples de ler e depurar do que o modelo reativo (como Spring WebFlux) — alcancem níveis de escalabilidade semelhantes ou até superiores ao lidar com cargas intensivas de I/O.

Configurando o Spring Boot 3.2 para Usar Virtual Threads

A partir do Spring Boot 3.2, o suporte para o Java 21 e Virtual Threads foi totalmente integrado de forma declarativa. Não é mais necessário criar configurações complexas de beans para substituir os executores padrão do servidor web.

Para ativar o recurso, o primeiro passo é garantir que o seu projeto esteja configurado para utilizar o JDK 21. No arquivo de definição do seu build (Maven ou Gradle), a versão de destino deve ser explicitamente definida.

Configuração com Maven (pom.xml)

Certifique-se de que a tag pai do Spring Boot está na versão correta e a propriedade do Java está definida para 21:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.0</version>
</parent>
<properties>
    <java.version>21</java.version>
</properties>

Ativação via Propriedades da Aplicação

Com a dependência do Spring Boot Starter Web adicionada, a ativação das Virtual Threads é realizada por meio de uma única propriedade no arquivo application.properties ou application.yml. Isso instrui o Spring Boot a configurar o Tomcat (ou outro servidor embarcado suportado) e os executores de tarefas assíncronas para utilizarem as threads do Projeto Loom.

No arquivo src/main/resources/application.properties, adicione:

spring.threads.virtual.enabled=true

Com esta propriedade ativa, o Tomcat passará a utilizar um Executor baseado em Virtual Threads para processar as requisições HTTP recebidas, eliminando o limite tradicional imposto pelo tamanho do pool de threads de plataforma.

Empacotamento da Aplicação Spring Boot e Java 21 com Docker

Para garantir a portabilidade e a consistência do ambiente ao executar testes de carga, é fundamental utilizar o Docker para empacotar a aplicação com o runtime adequado do Java 21.

Abaixo está um exemplo de Dockerfile multi-stage, projetado para compilar e executar a aplicação de maneira otimizada, reduzindo o tamanho final da imagem e garantindo que apenas os artefatos necessários sejam mantidos no contêiner de execução.

Exemplo de Dockerfile

# Estágio de Compilação
FROM maven:3.9.5-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests

# Estágio de Execução
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

Este Dockerfile utiliza a imagem base do Eclipse Temurin para o Java 21, garantindo total compatibilidade com as APIs do Projeto Loom e otimizando o consumo de recursos dentro do ambiente conteinerizado.

Executando Testes de Carga para Validar a Solução

Para comprovar a eficácia das Virtual Threads no tratamento de I/O bloqueante sob estresse, o teste de carga deve simular um cenário real onde as requisições sofrem atrasos artificiais de rede ou banco de dados. Um endpoint de teste simples pode simular essa latência com Thread.sleep().

No modelo tradicional de threads de plataforma, um atraso de 1 segundo por requisição esgotaria rapidamente o pool padrão do Tomcat (geralmente limitado a 200 threads), fazendo com que as requisições subsequentes fossem enfileiradas ou rejeitadas. Com a propriedade spring.threads.virtual.enabled=true ativada, a aplicação deve manter a estabilidade mesmo sob milhares de conexões simultâneas.

Ferramentas de teste de carga, como Apache Benchmark (ab), k6 ou Gatling, podem ser usadas para disparar requisições concorrentes contra a imagem executando no Docker. Durante a validação, observe as métricas de taxa de transferência (throughput) e taxa de erro, confirmando que o gargalo de I/O foi mitigado pelo comportamento eficiente das threads virtuais sob o Java 21.

Perguntas Frequentes (FAQ)

As Virtual Threads substituem completamente as threads tradicionais?

Não. As threads tradicionais (Platform Threads) ainda são recomendadas para tarefas intensivas de CPU, como processamento de imagens, criptografia pesada ou cálculos complexos. As Virtual Threads do Java 21 brilham especificamente em cenários limitados por I/O (I/O-bound), como chamadas HTTP e consultas a bancos de dados.

O Spring Boot configura automaticamente todos os pools para Virtual Threads?

Ao ativar a propriedade spring.threads.virtual.enabled=true, o Spring Boot configura automaticamente os principais componentes de execução para usar Virtual Threads. Isso inclui o servidor web integrado (Tomcat ou Jetty), o agendador de tarefas (TaskScheduler) e os executores de métodos assíncronos (@Async).

Existe algum cuidado com bibliotecas legadas ao migrar para as Virtual Threads?

Sim. O principal ponto de atenção é o "thread pinning" (fixação de thread), que ocorre quando uma operação bloqueante é executada dentro de um bloco sincronizado (synchronized) ou chama código nativo (JNI). Nesses casos, a Virtual Thread não consegue se desassociar da Carrier Thread, limitando temporariamente os benefícios do Projeto Loom.

Próximos Passos na Prática

A migração para o Java 21 e a ativação das Virtual Threads no Spring Boot 3.2 representam uma evolução fundamental para arquiteturas modernas de microsserviços e aplicações Web de alta escala. Antes de realizar a implantação em produção, certifique-se de submeter seu código a uma rigorosa revisão técnica. Toda veiculação/publicação exige aprovação humana antes de ir ao ar, garantindo que as configurações de contêineres e parâmetros da JVM estejam devidamente alinhados aos requisitos do seu ambiente operacional.