Command Palette

Search for a command to run...

PHASE 3Beginner Java 5+ ~27 min· topic 5 of 11

Topic 3.5

Variable Arguments (varargs)

In one line

A varargs parameter, written int... nums, lets a method accept any number of arguments of one type, including none. Inside the method it is an ordinary array, which the compiler builds for you at each call.

Think of it like this

A shopping bag at the till. You can put in one apple, five items, or nothing at all, and the cashier handles whatever is in the bag. You don't need a different bag for each number of items. A varargs parameter is that bag: the caller drops in as many values as it likes, and the method receives them packed together.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Varargs
Short for variable arguments: a parameter that accepts zero or more values of one type, written Type... name.
Arity
The number of parameters a method takes. A varargs method has variable arity.
Ellipsis
The three dots ... that mark a varargs parameter.
Empty array
An array with length 0. What a varargs parameter holds when the caller passes nothing.
Format string
A text template like "%s is %d" where %s and %d are replaced by arguments, used by printf and String.format.
Heap pollution
When a variable of a generic type refers to an object that isn't really of that type. Generic varargs can cause it, which is why the compiler warns.

Step by step

01Before varargs: overloads or arrays

Before Java 5, a method that summed "some numbers" needed either many overloads (sum(int a), sum(int a, int b), sum(int a, int b, int c)...) or an array parameter that forced callers to write sum(new int[] {1, 2, 3}).

Varargs keeps the array parameter inside the method but lets the caller write a natural list: sum(1, 2, 3).

02Declaring and calling

int... nums reads as "zero or more ints called nums". Inside the method you treat nums exactly like int[] nums.

Because it's an array, a caller who already has an array can pass it directly and no new array is made.

Main.javawhole filejava
static int sum(int... nums) {
    int total = 0;
    for (int n : nums) {
        total += n;
    }
    return total;
}

sum();                     // 0  (nums is an empty array)
sum(5);                    // 5
sum(1, 2, 3);              // 6
sum(new int[] {10, 20});   // 30 (the array is passed as is)

03What the compiler writes for you

At each call, the compiler counts the extra arguments and inserts the array creation. sum(1, 2, 3) becomes sum(new int[] {1, 2, 3}) in the bytecode, and sum() becomes sum(new int[0]).

In the class file, a varargs parameter is literally an array parameter plus a flag (ACC_VARARGS) that tells the compiler to allow the list syntax. That's why public static void main(String... args) is a perfectly valid main method.

What the compiler writes for youdiagram
Rendering diagram…

04Fixed parameters first, varargs last

You can mix normal parameters with one varargs parameter, as long as varargs comes last. The compiler fills the fixed parameters first, then packs whatever is left into the array.

This is exactly how printf works: printf("%s scored %d%n", "Asha", 92). The first argument is the format string; the rest become an Object[], with 92 boxed to an Integer.

Only one varargs parameter is allowed, because with two the compiler couldn't tell where one list ends and the next begins.

05Varargs loses to fixed-arity overloads

If both log(String msg) and log(String... msgs) exist, log("a") calls the single-parameter version: varargs methods are only considered in the last phase of overload resolution.

The JDK uses this for speed. List.of has fixed overloads for 0 to 10 elements plus a varargs version for more, so common calls don't allocate an extra array.

06The primitive-array trap with Object...

Arrays.asList(T... a) is generic, and generics only work with objects. Pass it an int[] and T becomes int[] itself: you get a list containing one element, the whole array. Pass an Integer[] and you get a list of three numbers.

The same happens with any Object... parameter: an int[] is an Object (all arrays are), so it's wrapped as a single argument, while an Integer[] is an Object[] and is passed as the array itself.

07null and varargs

Calling sum() gives an empty array, but explicitly passing a null array gives nums == null, and the loop then throws NullPointerException. Robust varargs methods either document that null isn't allowed or check for it.

With an Object... parameter, count(null) is a special puzzle: null could be the whole array or one element. The compiler treats it as the array and warns (non-varargs call of varargs method with inexact argument type for last parameter). Cast to say which you mean: (Object) null for one element, (Object[]) null for no array.

Try it yourself

  1. 1

    Write an average with varargs

    Write static double average(double... values) and call it with 0, 1 and 4 arguments. Decide what to return for zero values (0.0? Double.NaN?) and print all three results.

  2. 2

    Prove it's an array

    Inside sum, add System.out.println(nums.getClass().getSimpleName());. Run it: it prints int[] for every call, including sum().

  3. 3

    Pass null on purpose

    Call sum((int[]) null) and read the exception. Then add a guard if (nums == null) return 0; at the top of sum and run again.

Code & diagrams

Any number of arguments, including none Java 5+ New tab
Sign in to run this example in your browser.

Expected output

got 0 values -> 0
got 1 values -> 5
got 3 values -> 6
got 2 values -> 30
log(String): one
log(String...): 2 messages
log(String...): 0 messages
Fixed parameters plus varargs, and printf Java 5+ New tab
Sign in to run this example in your browser.

Expected output

red, green, blue
login -> cart -> pay
[]
Asha scored 92 out of 100
avg   | 81.7
The int[] vs Integer[] trap Java 5+ New tab
Sign in to run this example in your browser.

Expected output

count(int[])     = 1
count(Integer[]) = 3
asList(int[]).size()     = 1
asList(Integer[]).size() = 3

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Put the varargs parameter first

Declare static int total(int... nums, String label).

Main.javawhole filejava
public class Main {
    static int total(int... nums, String label) { return 0; }
    public static void main(String[] args) { }
}
terminal
$ javac Main.java
── what you'll see ──
Main.java:2: error: varargs parameter must be the last parameter
static int total(int... nums, String label) { return 0; }
^
1 error

Break #2

Overload int... and Integer...

Declare both f(int... x) and f(Integer... x), then call f(1).

Main.javawhole filejava
static void f(int... x)     { System.out.println("int..."); }
static void f(Integer... x) { System.out.println("Integer..."); }
public static void main(String[] args) {
    f(1);
}
terminal
$ javac Main.java
── what you'll see ──
Main.java:5: error: reference to f is ambiguous
f(1);
^
both method f(int...) in Main and method f(Integer...) in Main match
1 error

Break #3

Pass a null array

Call a varargs method with an array variable that is null.

Main.javawhole filejava
public class Main {
    static int sum(int... nums) { int t = 0; for (int n : nums) t += n; return t; }
    public static void main(String[] args) {
        int[] none = null;
        System.out.println(sum(none));
    }
}
terminal
$ java Main
── what you'll see ──
Exception in thread "main" java.lang.NullPointerException: Cannot read the array length because "<local2>" is null
at Main.sum(Main.java:2)
at Main.main(Main.java:5)

Myth vs fact

Myth

With no arguments, the varargs parameter is null.

Fact

It's an empty array of length 0. It's only null if a caller explicitly passes a null array.

Myth

Varargs is a special kind of parameter at run time.

Fact

At run time it's an ordinary array parameter. The only difference is a flag that lets the compiler accept a comma-separated list at call sites.

Myth

Arrays.asList(new int[] {1, 2, 3}) gives a list of three numbers.

Fact

It gives a List<int[]> with one element, because generics can't hold primitives. Use an Integer[], List.of(1, 2, 3) or IntStream.of(...).boxed().

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Each varargs call allocates an array. In hot code the JIT's escape analysis can often remove it, but not always; that's why List.of, EnumSet.of and logging APIs such as SLF4J's debug(String, Object, Object) provide fixed-arity overloads for the common cases.

  • ▸

    Generic varargs (static <T> List<T> listOf(T... items)) trigger the warning Possible heap pollution from parameterized vararg type, because Java can't create a generic array and builds an Object[]-like array instead. @SafeVarargs (Java 7; allowed on private instance methods from Java 9) promises the method doesn't store into or leak the array, and suppresses the warning at call sites (Topic 8.6).

  • ▸

    A varargs method can be overridden by one that takes a plain array and vice versa, with only a warning, because the descriptor is identical. Seniors keep the ... consistent to avoid confusing callers.

Remember this

  1. 1

    Write the type, three dots, then the name: static int sum(int... nums). Callers can write sum(), sum(5), sum(1, 2, 3), or pass an existing array: sum(new int[] {4, 5}).

  2. 2

    Inside the method, nums is just an array (int[]). Use nums.length, nums[0] and loops as usual. With no arguments it's an empty array, never null.

  3. 3

    The compiler does the packing: sum(1, 2, 3) is compiled as sum(new int[] {1, 2, 3}). So every call allocates a new array (usually cheap, sometimes optimised away).

  4. 4

    A method can have only one varargs parameter, and it must be last: static String join(String separator, String... parts) is fine; (int... nums, String label) doesn't compile.

  5. 5

    In overload resolution, varargs methods are tried last (phase 3, Topic 3.4), so a fixed-arity overload always wins when it matches.

  6. 6

    You already use varargs: System.out.printf(String format, Object... args), String.format, Arrays.asList(T... a) and List.of(E... elements) all take them.

Explain it without notes

01

What does the compiler do with a call like sum(1, 2, 3) to static int sum(int... nums)?

02

Why must the varargs parameter be the last one, and why only one per method?

03

Why does Arrays.asList(new int[] {1, 2, 3}).size() return 1?

04

Given log(String) and log(String...), which does log("x") call and why?

Practice

01

Write static int max(int first, int... rest) that returns the largest value. Requiring first means the method can never be called with zero values. Test max(4), max(4, 9, 2).

02

Write static String repeatEach(int times, String... words) that returns each word repeated times times, separated by spaces. Test repeatEach(2, "hi", "yo").

03

Write static boolean containsAll(String text, String... words) that returns true if text contains every word. Test it with "java is fun" and the words "java", "fun", then "java", "boring".

Trade-offs

  • ↔

    Varargs makes call sites clean, but each call allocates an array and the method can't require a minimum count except with fixed leading parameters (int first, int... rest).

  • ↔

    A varargs parameter accepts a caller's array as is, so the method shares (and could mutate) the caller's array. Copy it if you store it.

  • ↔

    Varargs and overloading together create confusing resolution rules. Keep at most one varargs overload per method name.

Done when you can

  • Done when you can declare and call a varargs method with zero, one and many arguments.

  • Done when you can explain that varargs is an array built by the compiler.

  • Done when you can combine fixed parameters with a varargs parameter correctly.

  • Done when you can explain the Arrays.asList(int[]) trap.

  • Done when you know varargs is the last choice in overload resolution.