Saltar al contenido
ecosistemadigital.dev
Java Destacado

Java 21 y virtual threads: cuándo sí, cuándo no y cómo medirlo

Los virtual threads no son una bala de plata. Una guía práctica para decidir dónde aportan valor, qué medir antes de migrar y qué trampas evitar en producción.

Yasser Pérez 3 min de lectura

Java 21 llegó con una de esas features que cambian la conversación: virtual threads. Desde entonces, cada vez que un servicio tiene latencia alta, aparece la misma pregunta: «¿y si activamos virtual threads?».

La respuesta corta es depende. La respuesta útil es entender qué problema resuelven, dónde no aplican y cómo demostrar con datos que la migración vale la pena.

Qué cambia realmente

Un hilo de plataforma es una abstracción uno-a-uno sobre un hilo del sistema operativo. Reservar miles de ellos para esperar respuestas de red es caro: memoria, contexto y planificación. Los virtual threads, en cambio, son gestionados por la JVM y se montan sobre un conjunto pequeño de carrier threads cuando necesitan ejecutar CPU.

La consecuencia práctica es simple: puedes escribir código bloqueante y legible, y aun así escalar a decenas de miles de tareas concurrentes que pasan la mayor parte del tiempo esperando I/O.

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    List<Future<Respuesta>> futures = ids.stream()
        .map(id -> executor.submit(() -> cliente.consultar(id)))
        .toList();

    for (Future<Respuesta> future : futures) {
        procesar(future.get());
    }
}

En Spring Boot 3.2+ la activación es explícita:

spring.threads.virtual.enabled=true

Pero activar la propiedad no es una decisión de arquitectura. Es una decisión de rendimiento que debe validarse.

Dónde brillan

  • Servicios con muchas llamadas a bases de datos, APIs o colas.
  • Gateways y proxies con alta concurrencia y poca CPU por petición.
  • Procesos batch que hoy mantienen pools complejos solo para esperar.
  • Código nuevo donde la simplicidad del modelo bloqueante importa.

Dónde no aportan (o pueden empeorar)

  • Cargas CPU-bound: la JVM no puede regalar núcleos. Agregar hilos solo agrega contención.
  • Librerías que usan pools internos con límites propios; el cuello de botella no desaparece, se mueve.
  • Acceso a bases de datos con pools pequeños: si el pool tiene 20 conexiones, tener 100.000 virtual threads no genera 100.000 consultas simultáneas.
  • Código con synchronized alrededor de operaciones bloqueantes en Java 21. El virtual thread queda pinned al carrier y pierde la ventaja.

Cómo medirlo sin engañarte

La métrica que importa no es «cuántos hilos tengo», sino cuánto trabajo útil entrego por unidad de recurso. Antes de migrar, define una línea base:

MétricaPor qué importa
p95 / p99 de latenciaDetecta colas ocultas y degradación bajo carga
Throughput (req/s)Confirma que no solo movimos el cuello de botella
CPU y memoria nativaLos virtual threads son baratos, pero no gratuitos
GC pause timeCambios de asignación pueden afectar la pausa
Errores y timeoutsUn pool saturado puede fallar de forma distinta

Prueba con el mismo tráfico, la misma base de datos y el mismo hardware. Si no puedes reproducir producción, al menos documenta las diferencias. Un benchmark sin condiciones comparables es una anécdota.

Checklist de adopción

  1. Identifica si el cuello de botella es I/O o CPU.
  2. Revisa pools de conexiones, límites de proveedores y rate limits.
  3. Busca synchronized en rutas calientes.
  4. Ejecuta pruebas de carga con y sin virtual threads.
  5. Mide latencia, throughput, CPU y memoria nativa.
  6. Despliega de forma incremental, con feature flag si es posible.
  7. Documenta la decisión y el contexto en un ADR.

El criterio que uso

Adopto virtual threads cuando el servicio pasa la mayor parte del tiempo esperando y el código es más claro sin gestión manual de pools. Los evito cuando la carga es computacional o cuando el límite real está en otro recurso.

La tecnología es excelente. Pero la arquitectura sigue siendo decidir dónde aplicarla, no activar una propiedad y esperar magia.

#Java 21 #Virtual Threads #Concurrencia #Rendimiento