skip to content

Why does a generic varargs method produce a heap-pollution warning at compile time?

level: seniorimportance: should knowfreq 40%

answer

  1. Varargs T... compiles to T[] array
  2. Arrays reifiable, generics non-reifiable — conflict
  3. Implicitly created array uses erased element type
  4. Leaked array reference = pollution risk
  5. Warning moved to declaration in Java 7

basics

~20 s

Varargs are really an array under the hood. With generics you would need an array of a generic type, which Java cannot fully create safely. So the compiler warns that this array could be misused and cause type errors later.

solid answer

~50 s

A varargs parameter like T... args is compiled into a T[] array. But arrays in Java are reifiable — they carry and check their element type at runtime — while generic types are non-reifiable due to erasure. You cannot create a true new T[], so the compiler creates an array of the erased type instead. That array's reference, typed as T[], can leak out of the method, and because its real runtime type does not match its static type, code can store a wrong-typed element into it without an immediate error. That is exactly heap pollution. Because the array is implicitly created from a non-reifiable type, the compiler emits an unchecked/heap-pollution warning at the method declaration (and at each call site). The warning is the compiler flagging that it could not guarantee the array is used type-safely.

code

java · 14 lines
java
// Generic varargs: 'T... args' becomes 'T[] args' under erasure.
static <T> T[] pick(T... args) { // <- heap-pollution warning here (pre-@SafeVarargs)
    return args;                  // returning/leaking the array is the risk
}

static <T> T[] dangerous(T a, T b) {
    return pick(a, b);
}

public static void main(String[] a) {
    // Under erasure the created array is Object[]; assigning to String[] can throw
    Object[] arr = dangerous("x", "y");
    arr[0] = Integer.valueOf(1); // pollutes if arr were viewed as String[]
}

go deeper

for a junior

Knows varargs are turned into an array and that combining them with generics produces a compiler warning.

for a middle

Explains that arrays are reifiable but generics are erased, so a generic varargs array's element type cannot be enforced at runtime.

for a senior

Articulates how the implicit erased-type array can leak and be polluted, knows the warning moved to the declaration in Java 7, and reasons about when the method is actually safe.

for a principal

Discusses the language design tension between reifiable arrays and erased generics, the rationale for relocating the warning, and guidelines for designing generic-varargs APIs that are provably safe.

## Background terms **Varargs** (variable-arity) let you call a method with any number of trailing arguments: `void log(String... parts)` can be called as `log("a", "b", "c")`. The compiler bundles those arguments into an **array** — `String[]` here. Inside the method, `parts` is an array. **Reifiable type**: a type whose full type information is available at runtime. Arrays are reifiable — a `String[]` knows it is a `String[]` and will throw `ArrayStoreException` if you try to store a non-String. Most generic types are **non-reifiable**: due to **type erasure**, `List<String>` becomes just `List` at runtime, losing the `<String>`. ## The conflict: arrays are reifiable, generics are not Varargs use arrays. Generics are erased. Put them together — a **generic varargs** method like `<T> void f(T... args)` or `void g(List<String>... lists)` — and you ask for an array of a non-reifiable type. Java forbids creating such arrays directly: `new T[n]` and `new List<String>[n]` are compile errors, precisely because a reifiable array of a non-reifiable element type cannot enforce its own type at runtime. But varargs *must* build an array to pass the arguments. So the compiler does it for you implicitly, creating an array of the **erased** element type — effectively a `List[]` for `List<String>...`. This array is then handed to your method typed as `List<String>[]`, even though its true runtime identity is just `List[]`. ## Why that is dangerous (heap pollution) Because the array's static type (`List<String>[]`) is a lie about a non-reifiable element type, you can pollute it: ```java @SafeVarargs // remove this and you see the warning static <T> T[] toArray(T... args) { return args; } static <T> T[] danger(T a, T b) { return toArray(a, b); // creates a Object[] under erasure for some calls } static void main() { String[] strings = danger("x", "y"); // ClassCastException possible } ``` The classic failure: a method receives `T... args`, and through erasure the created array is `Object[]`. If a reference to it escapes typed as something narrower (e.g. assigned where a `String[]` is expected), a later access throws `ClassCastException` — or you can store the wrong type into the leaked array. The array reference leaking out of the method is the key risk. ## Why the warning fires at the *declaration* Before Java 7, the unchecked warning appeared at every **call site**, which was noisy and blamed callers for the author's design. Java 7 moved the primary warning to the **method declaration** — that is where the author decides whether the generic-varargs array is used safely. The author can then either fix the design or assert safety with `@SafeVarargs`, which suppresses the warning at both the declaration and all call sites. ## Summary The warning exists because varargs force the creation of an array (a reifiable construct) out of a generic (non-reifiable) element type. The result is an array whose static type overstates what the runtime can enforce, which is the textbook setup for heap pollution. The compiler cannot prove the method uses that array safely, so it warns and asks the author to take responsibility.

  • Why can you not just write `new T[n]` to avoid the implicit array?
    Because T is non-reifiable; a `T[]` could not enforce its element type at runtime, so Java disallows creating it directly. Varargs sidesteps this by implicitly making an array of the erased type, which is exactly why it is unsafe.
  • Before Java 7, where did this warning appear, and why was that changed?
    At every call site, which was noisy and blamed callers. Java 7 moved it to the method declaration where the author controls safety, and let the author suppress it once with @SafeVarargs.

saying these in an interview costs you the question

  • Saying arrays are non-reifiable (they are reifiable; generics are not)
  • Claiming `new T[n]` is allowed
  • Thinking the warning means the method definitely has a bug rather than that safety could not be proven
  • Confusing the varargs array with the contents being passed

context