Heap Pollution and SafeVarargs

Naveen AnamaneniUpdated · 4 min read
hardJava 25javagenericsjvm

Generics promise that a List<String> holds only strings. Heap pollution is when that promise is broken: a variable of type List<String> ends up pointing to a list that contains something else. Because of type erasure, the JVM can't notice when it happens. The problem surfaces later as a ClassCastException somewhere unexpected. It can only happen through code the compiler has already warned you about: raw types, unchecked casts, and generic varargs.

Polluting Through Varargs

A varargs parameter like List<String>... lists is really an array, List<String>[]. Arrays don't check generic types at runtime, so a method can store the wrong kind of list in it:

Poll.java
import java.util.ArrayList;
import java.util.List;
 
public class Poll {
    static void pollute(List<String>... lists) {
        Object[] array = lists;
        array[0] = List.of(42);
        String s = lists[0].get(0);
    }
 
    public static void main(String[] args) {
        pollute(new ArrayList<>(List.of("Navika")));
    }
}

The compiler warns at both ends:

Compiler output (javac -Xlint:unchecked)
Poll.java:5: warning: [unchecked] Possible heap pollution from parameterized vararg type List<String>
    static void pollute(List<String>... lists) {
                                        ^
Poll.java:12: warning: [unchecked] unchecked generic array creation for varargs parameter of type List<String>[]
        pollute(new ArrayList<>(List.of("Navika")));
               ^
2 warnings

And the program crashes on a line that contains no cast at all:

Terminal output
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
    at Poll.pollute(Poll.java:8)
    at Poll.main(Poll.java:12)
A List<String> variable now points to a list of numbers

How it works

  1. lists is a List<String>[], but at runtime it's just an array of List.
  2. Viewing it as Object[], the method stores a List containing 42. The array accepts it because it's still a List.
  3. lists[0] is typed List<String> but now refers to a list of integers: the heap is polluted.
  4. lists[0].get(0) runs the compiler's hidden cast to String and fails.

When @SafeVarargs Is Honest

Generic varargs are useful, so most of them are perfectly safe: List.of(...), Arrays.asList(...) and Set.of(...) all take T.... A method is safe if it only reads the varargs array and never stores into it or lets it escape. Such a method can be annotated @SafeVarargs, which tells the compiler "I promise" and removes the warnings for every caller:

SafeJoin.java▶ Try it
import java.util.ArrayList;
import java.util.List;
 
public class SafeJoin {
    @SafeVarargs
    static <T> List<T> joinAll(List<? extends T>... lists) {
        List<T> result = new ArrayList<>();
        for (List<? extends T> list : lists) {
            result.addAll(list);
        }
        return result;
    }
 
    public static void main(String[] args) {
        List<String> team =
            joinAll(List.of("Navika"), List.of("Srihitha", "Advitha"));
        System.out.println(team);
    }
}
Output
[Navika, Srihitha, Advitha]

joinAll only reads each list, so @SafeVarargs is truthful here.

@SafeVarargs rules
Allowed onstatic methods, final instance methods, private instance methods, and constructors
Not allowed onMethods that could be overridden, since a subclass could break the promise
Honest only ifThe method never writes into the varargs array and never exposes it (for example by returning it)

Common Mistakes

Ignoring "possible heap pollution" warnings. Each one is a place where the generic guarantee can break.

Returning or storing the varargs array. That lets callers see a T[] that might hold the wrong type.

Mixing raw types with generic ones. A raw List can put anything into a List<String> without any cast in sight.

Practice

  1. Easy: Remove @SafeVarargs from joinAll, compile with -Xlint:unchecked, and read the warnings.
  2. Harder: Write static <T> T[] toArray(T... items) { return items; } and call it from a generic method. Explain why returning the varargs array is unsafe.

Quick check: Heap pollution

4 questions
1What is heap pollution?
2What makes generic varargs risky?
3Which methods can be annotated @SafeVarargs?
4What does @SafeVarargs actually do?

Key Takeaways

Was this tutorial helpful?

Lesson 250 of 482