Java Varargs: Variable-Length Argument Lists
Varargs (variable-length argument lists) let a method accept any number of arguments of the same type without the caller constructing an array manually. Declared with the ... syntax, a varargs parameter is treated as an array inside the method body. This article covers varargs syntax, how the JVM implements them, ordering rules, overloading interactions, passing existing arrays, and the heap-pollution warning that arises with generic varargs.
What Are Varargs and Why Do They Exist?
Before Java 5, a method that needed to accept an arbitrary number of values of the same type had two options: declare several overloads (log(String)log(String, String)log(String, String, String)
Varargs, introduced in Java 5, let you write void log(String... messages)log("a")log("a", "b")log("a", "b", "c")log()String...String[]
Familiar APIs that use varargs: String.format(String, Object...)List.of(E...)Arrays.asList(T...)System.out.printf(String, Object...)Assertions.assertAll(Executable...)
Varargs Syntax and the Array Under the Hood
A varargs parameter is declared with three dots immediately after the type name. It must be the last parameter in the method signature: only one varargs parameter is allowed per method, and it must come after all fixed parameters. Inside the method body, the varargs parameter is an ordinary array of the declared type; you can iterate it with a for-each loop, check its length
// Declare a varargs method
public static int sum(int... numbers) {
// 'numbers' is an int[] inside the method body
int total = 0;
for (int n : numbers) {
total += n;
}
return total;
}
// Call with zero, one, many arguments — all valid
System.out.println(sum()); // 0
System.out.println(sum(5)); // 5
System.out.println(sum(1, 2, 3)); // 6
System.out.println(sum(10, 20, 30, 40)); // 100
// Mixing fixed and varargs parameters — varargs must be last
public static String format(String prefix, Object... values) {
StringBuilder sb = new StringBuilder(prefix).append(": ");
for (int i = 0; i < values.length; i++) {
if (i > 0) sb.append(", ");
sb.append(values[i]);
}
return sb.toString();
}
System.out.println(format("Items", "apple", 42, true));
// Items: apple, 42, true
List.of(E e1, E e2, ...)Passing an Existing Array to a Varargs Method
Because a varargs parameter is just an array at the bytecode level, you can pass an existing array directly: no wrapping required. The compiler detects that the argument type matches the array type and passes it through without creating a new array. This also means you can store varargs arguments in a variable and pass the same array to multiple calls.
int[] existing = {1, 2, 3, 4, 5};
System.out.println(sum(existing)); // 15 — array passed directly, no copy
String[] names = {"Alice", "Bob", "Carol"};
System.out.println(String.join(", ", names)); // Alice, Bob, Carol
// Caution: modifying the array inside the method affects the original
public static void zeroOut(int... nums) {
Arrays.fill(nums, 0); // modifies the underlying array
}
int[] data = {1, 2, 3};
zeroOut(data);
System.out.println(Arrays.toString(data)); // [0, 0, 0] — original was modified!
Varargs and Method Overloading
Varargs interact with method overloading in a potentially confusing way. The compiler treats a varargs method as a lower-priority candidate during overload resolution. If a fixed-arity overload matches a call exactly, the fixed-arity version is chosen. Only when no fixed-arity overload matches does the compiler consider the varargs option. This means you can provide both a sum(int a, int b)sum(int... numbers)
public static String describe(Object obj) {
return "single: " + obj;
}
public static String describe(Object... objs) {
return "varargs: " + Arrays.toString(objs);
}
System.out.println(describe("hello")); // single: hello (exact match)
System.out.println(describe("a", "b")); // varargs: [a, b]
System.out.println(describe((Object) null)); // single: null (explicit cast)
System.out.println(describe((Object[]) null)); // varargs called with null array
// — 'objs' will be null inside method!
Note: Calling a varargs method with a null(Object[]) null(Object) null
Generic Varargs and @SafeVarargs
When a varargs parameter has a generic type, for example T... itemsList<String>... listsObject[]
If your generic varargs method does nothing unsafe with the array: specifically, if it only reads from it and does not store foreign-typed values into it or expose the array reference externally: you can suppress the warning with @SafeVarargsstaticfinalprivate
// Safe — only reads from the varargs array, never writes foreign types into it
@SafeVarargs
public static <T> List<T> listOf(T... items) {
return new ArrayList<>(Arrays.asList(items));
}
List<String> names = listOf("Alice", "Bob", "Carol");
// UNSAFE example — do NOT do this (heap pollution)
@SuppressWarnings("unchecked") // needed because @SafeVarargs cannot be used here
static <T> T[] dangerousCombine(T[]... arrays) {
// Returning a generic array exposes the Object[] to callers
// who expect T[] — ClassCastException at the call site
return arrays[0]; // unsafe — do not expose the raw array
}
@SafeVarargsCollections.addAllEnumSet.ofList.of@SafeVarargs