Varargs (número variable de argumentos)

Intermedio
Java
Java
Actualizado: 27/04/2026

Varargs en métodos Java

flowchart LR
    Sig[método Tipo... args] --> Comp[Compilador genera array]
    Comp --> Arr[Tipo array detrás de escena]
    Arr --> Loop[Iterar como array]
    Sig --> Call1[método]
    Sig --> Call2[método 1]
    Sig --> Call3[método 1 2 3]
    Sig --> Call4[método new int 1 2]
    Sig --> Rules[Solo último parámetro]
    Sig --> Generic[Heap pollution warning]

El problema: número variable de argumentos

Antes de Java 5, si querías escribir un método que aceptase un número variable de argumentos tenías que sobrecargar el método muchas veces o recibir un array:

// Antes de varargs: muchas sobrecargas
public static int max(int a, int b) { ... }
public static int max(int a, int b, int c) { ... }
public static int max(int a, int b, int c, int d) { ... }

// O recibiendo array (incómodo de llamar)
public static int max(int[] numeros) { ... }
// Uso: max(new int[]{1, 2, 3});

Desde Java 5, los varargs resuelven esto con una sintaxis elegante.

Sintaxis

Un parámetro varargs se declara con tipo... seguido del nombre. Debe ser el último parámetro del método:

public static int max(int... numeros) {
    int mayor = Integer.MIN_VALUE;
    for (int n : numeros) {
        if (n > mayor) mayor = n;
    }
    return mayor;
}

Y se llama con cualquier número de argumentos:

max(1, 2, 3);
max(10, 20, 30, 40, 50);
max(); // sin argumentos: numeros es un array vacío

Internamente es un array

Dentro del método, el parámetro varargs es un array normal del tipo declarado:

public static void info(String... palabras) {
    System.out.println("Recibidas " + palabras.length + " palabras:");
    for (int i = 0; i < palabras.length; i++) {
        System.out.println(i + ": " + palabras[i]);
    }
}

Puedes iterarlo con for-each, usar índices y preguntar por length. Es exactamente un String[].

Combinar con parámetros obligatorios

Los parámetros obligatorios van antes del varargs:

public static String unir(String separador, Object... valores) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < valores.length; i++) {
        if (i > 0) sb.append(separador);
        sb.append(valores[i]);
    }
    return sb.toString();
}

// Uso
unir(", ", "Ana", "Bob", "Carmen"); // "Ana, Bob, Carmen"
unir(" | ", 1, 2, 3); // "1 | 2 | 3"

Solo puede haber un varargs por método y debe ser el último parámetro. Esto es ilegal:

// ERROR: varargs no puede ir antes de otro parámetro
public static void x(int... nums, String nombre) { }

Pasar un array a un varargs

Si ya tienes un array, puedes pasarlo directamente:

int[] datos = {5, 2, 8, 1, 9};
int mayor = max(datos); // válido

Para pasar una colección, convierte primero:

List<String> nombres = List.of("Ana", "Bob");
String resultado = unir(", ", nombres.toArray());

Ejemplos en la API estándar

El JDK usa varargs constantemente:

// String.format
String.format("Nombre: %s, edad: %d", "Ana", 30);

// List.of, Set.of, Map.of (Java 9+)
List<Integer> lista = List.of(1, 2, 3, 4, 5);
Set<String> set = Set.of("a", "b", "c");

// Arrays.asList
List<Integer> otros = Arrays.asList(10, 20, 30);

// Collections.addAll
Collections.addAll(lista, 6, 7, 8);

// Stream.of
Stream<String> s = Stream.of("x", "y", "z");

// Path.of (NIO)
Path p = Path.of("home", "user", "docs");

Sobrecarga y ambigüedades

La sobrecarga con varargs puede generar resolución ambigua. Java prefiere siempre la versión más específica:

public static void log(String mensaje) { System.out.println("string: " + mensaje); }
public static void log(String... mensajes) { System.out.println(mensajes.length + " strings"); }

log("uno"); // llama a log(String): más específica
log("uno", "dos"); // llama a log(String...)
log(); // llama a log(String...) con array vacío

Cuando haya duda, el compilador elige la no-varargs si cuadra; si no, la varargs.

Varargs de tipos genéricos: advertencia @SafeVarargs

Los varargs de tipos genéricos (List<String>...) generan un warning porque el array interno no preserva el tipo genérico (type erasure). Si tu método es seguro, añade @SafeVarargs:

@SafeVarargs
public static <T> List<T> unirListas(List<T>... listas) {
    List<T> resultado = new ArrayList<>();
    for (List<T> l : listas) {
        resultado.addAll(l);
    }
    return resultado;
}

@SafeVarargs solo puede aplicarse a métodos static, final o constructores (donde no puede haber polimorfismo que rompa la seguridad).

Buenas prácticas

  • Usa varargs cuando el número de argumentos típico sea pequeño y variable (0-5). Para listas de tamaño arbitrario, prefiere recibir List<T> o Collection<T>.
  • No abuses de varargs para aceptar "cualquier cosa" con Object...: pierdes seguridad de tipos.
  • Cuidado al sobrecargar métodos con varargs; puede haber conflictos con autoboxing o covariance.
  • Declara colecciones inmutables con List.of(...) / Set.of(...) / Map.of(...): usan varargs por debajo.

Caso B2B: trazabilidad y logging estructurado

En entornos de banca y telco con normativas de auditoría (DORA, PSD2, ENS), el logging estructurado se modela frecuentemente con métodos varargs sobre Logger.info(String formato, Object... args). SLF4J 2.x y Logback 1.5 usan exactamente esta firma. La organización gana porque vuestro equipo puede sustituir tanto el formato como los valores sin construir cadenas previamente, lo cual evita evaluaciones costosas cuando el nivel de log está desactivado en producción.

En retail, una API de catálogo que recibe filtros opcionales se beneficia de varargs en métodos de búsqueda: productoRepository.findByCategorias(Categoria... cats) permite llamadas con 0, 1 o N categorías sin sobrecargas múltiples. El equipo de plataforma reduce el código duplicado y la documentación queda más limpia para los equipos consumidores.

En administraciones públicas con sistemas de tramitación, validadores que aceptan reglas dinámicas (Validador.aplicar(Documento doc, Regla... reglas)) usan varargs para que cada expediente pueda construir su pipeline de validación según el tipo procedimental, sin imponer una colección obligatoria.

Versiones y rendimiento

Varargs existe desde Java 5 (2004) y forma parte del lenguaje base. Java 21 LTS y Java 25 LTS lo mantienen sin cambios. El compilador genera un array oculto en cada llamada, con coste de asignación en el heap: en bucles internos calientes (más de un millón de invocaciones por segundo), pasar un array preconstruido reduce la presión sobre el GC. Las JVM modernas (HotSpot, GraalVM) hacen escape analysis y suelen elidir el array si no se filtra fuera del método, pero conviene confirmar con perfiladores como JFR o async-profiler.

Anti-patrones y pitfalls

Object... args como vertedero de tipos. Algunos equipos declaran métodos procesar(Object... args) para esquivar el sistema de tipos. Esto pierde seguridad estática y desplaza errores a runtime. Si vuestro código necesita aceptar varios tipos, definid una jerarquía sellada (sealed interface Parametro) e iterad con pattern matching.

Heap pollution con genéricos. List<String>... listas aceptado sin @SafeVarargs provoca un unchecked warning y, peor, permite asignar un Object[] con List<Integer> dentro. En revisiones de código de banca, esta es una de las observaciones recurrentes de SonarQube.

Crear arrays innecesarios. metodo() con varargs vacío crea un array de longitud 0 cada llamada (las JVM modernas lo cachean en muchos casos, pero no hay garantía). Para puntos calientes, ofreced sobrecargas con 0 y 1 argumento explícitos.

Pasar null como varargs. metodo(null) se interpreta como pasar el array completo nulo, no un único argumento null. Esto provoca NullPointerException al intentar length. Forzad el casting: metodo((Object) null).

Comparativa con alternativas

Frente a Kotlin (vararg), la sintaxis es equivalente pero Kotlin permite el operador spread *array para pasar arrays existentes, más explícito que el comportamiento implícito de Java.

Frente a Scala (args: String*) y Groovy (también String...), Java es interoperable: una API Java con varargs se consume idiomáticamente desde ambos lenguajes JVM.

Frente a recibir List<T> directamente, varargs es más cómodo en sitios de llamada cortos (constructor de tests, factory methods), pero List<T> rinde mejor cuando los datos ya vienen en colección y evita la doble copia.

Documentación oficial

La especificación está en la JLS (Java Language Specification) §8.4.1 "Formal Parameters" y §15.12.4.2 "Evaluate Arguments". La Javadoc de String.format, List.of, Stream.of y Logger.info muestran usos canónicos en la plataforma. Para @SafeVarargs, consultad la Javadoc de java.lang.SafeVarargs para Java 21+.

Los varargs son una herramienta de conveniencia para APIs limpias; bien usados mejoran legibilidad, mal usados esconden costes de creación de arrays. En contextos B2B donde vuestro equipo prioriza claridad sobre microoptimización, son la elección por defecto siempre que la cardinalidad esperada sea baja y la legibilidad lo justifique.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Java es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Java

Explora más contenido relacionado con Java y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Declarar métodos con la sintaxis tipo... nombre. Comprender que varargs internamente es un array. Combinar parámetros obligatorios con varargs. Llamar a varargs con argumentos, array o colección (toArray()). Evitar ambigüedades al sobrecargar métodos con varargs. Conocer ejemplos reales: String.format, List.of, Arrays.asList.