Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

In Java, three ASCII periods (...) declare a variable-arity parameter, usually called varargs. They are not a generics operator or wildcard. A declaration such as <T> void print(T... values) combines a generic type parameter with varargs; the generic type is T, and the ellipsis lets callers supply zero or more values. Generic varargs need care because their array-like representation can produce unchecked warnings and, if misused, heap pollution.

One important distinction: the typographic ellipsis … (U+2026) is not Java syntax. Java source uses three separate ASCII periods: ....

What does ... mean in Java?

In a method declaration, ... marks the final parameter as variable-arity: callers may provide zero or more arguments of that parameter’s element type.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
static void printAll(String... values) {
    for (String value : values) {
        System.out.println(value);
    }
}

These calls are valid:

printAll();                         // zero values
printAll("A", "B", "C");           // separate values
String[] names = {"A", "B"};
printAll(names);                    // an existing array

Inside the method, values behaves like an array: it has a length, can be indexed, and can be traversed with an enhanced for loop. A varargs parameter must be last, so void okay(String prefix, int... values) is valid, but void notOkay(int... values, String suffix) is not. The Java Language Specification’s variable-arity rules define the declaration form.

Varargs are array-like, but they are not identical to an ordinary array parameter at the call site:

static void varargs(String... values) {}
static void arrayOnly(String[] values) {}

varargs("A", "B");       // valid
arrayOnly("A", "B");     // compile-time error

String[] values = {"A", "B"};
varargs(values);           // valid
arrayOnly(values);         // valid

A varargs call with separate arguments packages them into an array for the method. If you already have an array, you can pass it directly. The method-invocation rules also treat variable-arity applicability as a distinct stage of overload resolution.

How varargs combines with generics

A generic method can use a type variable as its varargs element type:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
static <T> void print(T... values) {
    for (T value : values) {
        System.out.println(value);
    }
}

print("one", "two");
print(1, 2, 3);
print(List.of("A"), List.of("B"));

Here <T> declares the type variable. The compiler can infer a suitable T from a call. T... says the method accepts a variable number of values of that type. It is conceptually similar to a final T[] parameter, but unlike an array-only parameter, it permits separate arguments.

The same combination can appear in a generic class:

class Collector<T> {
    void collect(T... values) {
        for (T value : values) {
            System.out.println(value);
        }
    }
}

The ellipsis does not declare or infer T; it controls how many arguments a caller may supply. For a broader overview of Java type parameters, inference, wildcards, and erasure, see Dev.java’s generics guide.

Why generic varargs can trigger warnings

Consider a varargs parameter whose element type is parameterized:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
static void addLists(List<String>... lists) {
    for (List<String> list : lists) {
        System.out.println(list);
    }
}

A compiler commonly warns that this declaration may cause heap pollution. List<String> is non-reifiable: after type erasure, the runtime does not retain the String type argument in a way that lets an array check enforce it. Arrays, by contrast, carry their component type at runtime. Java generally cannot create an array whose runtime component type is a parameterized type such as List<String>. See the JLS sections on reifiable types and type erasure.

Heap pollution means that a variable of a parameterized type refers to an object that does not satisfy the type argument its code assumes. The problem can be introduced at a varargs boundary and only become visible later:

static void unsafe(List<String>... lists) {
    Object[] array = lists;
    array[0] = List.of(42);          // generic mismatch is not checked here
    String value = lists[0].get(0);  // may fail when the value is used
}

This is deliberately unsafe. The warning does not mean every generic varargs method is broken; it signals that the array-like boundary cannot fully verify the generic type at runtime. Risk increases if code writes to the array, returns it, stores it, or exposes it to code that may retain or mutate it. A later ClassCastException may occur far from the point where the bad value entered.

When @SafeVarargs is appropriate

@SafeVarargs tells the compiler that the author has reviewed a varargs method or constructor and asserts that its use of the parameter is safe. It suppresses the relevant warning; it does not make unsafe code safe. Under the current language rules, it may annotate a static, final, or private method, or a constructor.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
@SafeVarargs
static <T> void print(T... values) {
    for (T value : values) {
        System.out.println(value);
    }
}

This read-only implementation uses the elements without modifying or exposing the array. Before adding the annotation, check that the method does not write incompatible values into the array, return or store it where it could be misused, or hand it to code that may retain or mutate it. An annotation that merely hides a warning is not a safety fix. The Java API documentation for SafeVarargs describes its purpose and restrictions.

How the common symbols differ

Syntax Meaning Example
<T> Declares a type variable on a generic method or type. <T> void use(T item)
List<T> A parameterized type using type variable T. List<String>
? A wildcard: an unknown type argument. List<?>
? extends T A wildcard with an upper bound. List<? extends Number>
? super T A wildcard with a lower bound. List<? super Integer>
<> The diamond operator, which lets the compiler infer constructor type arguments. new ArrayList<>()
... Marks a variable-arity parameter. String... values
[] Array declaration or access syntax. String[] values

A wildcard and varargs answer different questions. In List<?>, ? means “a list of some unknown element type.” In String..., the ellipsis means “zero or more strings.” They can appear together:

static void printLists(List<?>... lists) {
    for (List<?> list : lists) {
        System.out.println(list);
    }
}

Do not assume that adding a wildcard automatically removes every warning; the exact parameter type and varargs declaration matter. Also, List<Object> is not the same as List<?>: the former is a list whose element type is specifically Object, while the latter can refer to a list with any element type. See Oracle’s explanation of unbounded wildcards.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common traps and alternatives

Generic array creation

These declarations are illegal because Java cannot create arrays with a non-reifiable component type:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
// T[] values = new T[10];
// List<String>[] lists = new List<String>[10];

Use a collection when you need storage that grows or holds generic values:

List<T> values = new ArrayList<>(10);

When an actual array is required, a caller-supplied array factory can preserve the runtime component type:

static <T> T[] create(int size, IntFunction<T[]> factory) {
    return factory.apply(size);
}

String[] names = create(10, String[]::new);

A cast from new Object[10] to T[] is not automatically safe; use it only when a carefully maintained invariant justifies it. The JLS explains which types are reifiable in its type rules.

Null is not the same as no arguments

printAll();                    // an empty varargs array
printAll((String) null);       // one null element
printAll((String[]) null);     // a null array reference

The last call passes a null reference, so code that iterates the parameter without checking may throw NullPointerException. If the API permits that call, handle it deliberately. An uncast printAll(null) can be confusing, particularly when overloads are involved; an explicit cast makes the intended call clearer.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Overloads can change which method is called

static void log(String value) {
    System.out.println("single");
}

static void log(String... values) {
    System.out.println("varargs");
}

log("one"); // selects the fixed-arity overload

Java considers applicable fixed-arity methods before variable-arity invocation. Adding a varargs overload to an existing API can therefore change behavior or create ambiguity for some calls, especially around null, boxing, widening, and generic inference. For complex overload sets, consult the JLS rules for determining applicability and choosing the most specific method.

Choose the parameter shape that fits the input

Parameter Use it when Trade-off
T... values The method naturally accepts a convenient zero-or-more list of arguments. Call-site friendly, but generic element types can trigger warnings; callers can also pass a null array.
T[] values The method specifically requires an array, often because the caller already has one. Explicit array contract; callers cannot supply separate values.
List<T> values The input is conceptually a collection, or the method needs collection operations. A collection avoids the generic-array boundary, but callers pass a collection rather than separate arguments.
List<?> values The method can work with a list without knowing its element type. Useful for read-only or type-agnostic operations; the wildcard does not promise elements of a particular type.

For example, if the values are already a group of lists, a collection parameter is often clearer and avoids the generic-varargs boundary:

static <T> void process(List<List<T>> groups) {
    for (List<T> group : groups) {
        // process each group
    }
}

process(List.of(List.of("A", "B"), List.of("C")));

Use bounded wildcards when the method needs a type relationship rather than a variable argument count. For example, List<? extends Number> can supply numbers to a read-only summing method. The teaching mnemonic “producer extends, consumer super” can help when designing bounded-wildcard APIs, but it is a design aid rather than a separate Java language rule.

What to do about a heap-pollution warning

  1. Inspect whether the method mutates, stores, returns, or exposes its varargs array.
  2. If it does, decide whether the API can instead accept a collection or an explicitly typed array.
  3. If the method only reads values and its safety can be justified, consider @SafeVarargs where the declaration is eligible.
  4. Keep the safety argument tied to the implementation: later changes that expose or mutate the array may invalidate it.
  5. Do not suppress unrelated unchecked warnings simply to make compiler output quiet.

In short: ... controls argument count; generics control type relationships. They can be combined, but when the element type is non-reifiable, treat the resulting warning as a request to inspect the array boundary, not as a message to silence automatically.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For current language rules, use the Java SE 26 edition of the JLS. Oracle notes that its classic generics tutorial was written for JDK 8; it remains useful for examples, while the current specification is the normative source for language rules.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.