Generics

This section documents the current Java release line, with Java 25 LTS as the reference point (no specific patch version is pinned), as published at the Java developer portal, the Java Tutorials, and the Java SE API specification — which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Generics let a class, interface, or method be written once and used with many types while the compiler still checks every use. This page follows dev.java "Generics" and the Java Tutorials "Generics" trail.

Generic Classes, Interfaces, and Methods

A type parameter in angle brackets after the type name stands in for a type supplied at each use site. By convention it is a single upper-case letter: E for element, K and V for key and value, T and U for arbitrary types, R for a result. See Generic Types.

import java.util.function.Function;

public final class Box<T> {

    private T value;

    public Box(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }

    public void set(T value) {
        this.value = value;
    }

    // a generic method: its own type parameter R, inferred from the argument
    public <R> Box<R> map(Function<? super T, ? extends R> fn) {
        return new Box<>(fn.apply(value));
    }
}

Box<String> b = new Box<>("hi");        // diamond <>: T inferred as String from the declared type
String s = b.get();                     // no cast needed
Box<Integer> length = b.map(String::length);

The diamond operator <> (Java 7+) tells the compiler to infer the constructor’s type arguments from context, so new Box<String>(…​) need not be repeated. A generic interface behaves the same way — List<E>, Comparable<T>, Function<T, R> — and an implementing class either fixes the parameter (class Names implements List<String>) or stays generic (class MyList<E> implements List<E>).

Generic methods

A method may declare its own type parameters, written before the return type. They are usually inferred from the arguments, so callers rarely spell them out. See Generic Methods.

import java.util.Collection;
import java.util.Iterator;
import java.util.List;

static <T> List<T> listOf(T a, T b, T c) {
    return List.of(a, b, c);
}

static <T extends Comparable<? super T>> T max(Collection<? extends T> items) {
    Iterator<? extends T> it = items.iterator();
    T best = it.next();
    while (it.hasNext()) {
        T next = it.next();
        if (next.compareTo(best) > 0) {
            best = next;
        }
    }
    return best;
}

var xs = listOf(3, 1, 4);                 // T inferred as Integer
int m = max(xs);                          // T inferred as Integer
var mixed = Demo.<Number>listOf(1, 2.0, 3L);   // explicit type witness, rarely needed

Bounded Type Parameters

<T extends Bound> restricts T to Bound or its subtypes and lets the method body call Bound’s members. Despite the keyword, `extends here covers both classes and interfaces. See Bounded Type Parameters.

import java.util.List;

static <T extends Number> double sum(List<T> numbers) {
    double total = 0;
    for (T n : numbers) {
        total += n.doubleValue();       // doubleValue() is available: T is known to be a Number
    }
    return total;
}

A recursive bound such as <T extends Comparable<T>> reads as "T must be comparable to itself" and is the standard signature for ordering-aware generic code.

Multiple bounds

When a type parameter must satisfy several types at once — a class plus one or more interfaces, or just several interfaces — list every requirement after extends, joined with & (not a comma). The parameter’s effective type is the intersection of all the bounds, so the method body may call members declared by any of them. The rules:

  • At most one bound may be a class, and if present it must be written first. All the other bounds must be interfaces (order among them does not matter).

  • Erasure replaces T with its first bound (Object if the first bound is an interface), so put the bound whose members you use most, or the class, first — it is the type that appears in the compiled signature and in stack traces.

  • A bound may itself be parameterised (Comparable<T>), including recursively.

import java.io.Serializable;

// T must be a Number, be comparable to itself, AND be serializable
static <T extends Number & Comparable<T> & Serializable> T clamp(T value, T low, T high) {
    if (value.compareTo(low) < 0) {     // from Comparable<T>
        return low;
    }
    if (value.compareTo(high) > 0) {
        return high;
    }
    return value;                        // Number + Serializable also usable in the body
}

int c = clamp(15, 0, 10);               // Integer is Number & Comparable<Integer> & Serializable -> 10

A common real-world shape is "a concrete base class, refined by a capability interface" — for example a method that accepts any AbstractList implementation that is also RandomAccess, so it can index safely in a loop:

import java.util.AbstractList;
import java.util.RandomAccess;

static <T, L extends AbstractList<T> & RandomAccess> T middle(L list) {
    return list.get(list.size() / 2);   // O(1) get() -- guaranteed by RandomAccess
}

If the intersection type is only needed in one place, it can also appear directly in a cast: ((Number & Comparable<?>) value).

Variance: Invariance, Covariance, and Contravariance

Variance describes how subtyping between type arguments carries over to the generic type built from them. Java generics are invariant by default: even though Integer is a subtype of Number, List<Integer> is not a subtype of List<Number>. This is deliberate — if the assignment were allowed, you could alias a List<Integer> as a List<Number> and then add(3.14) to it, corrupting the original list with no compile error and no ClassCastException at the add.

List<Integer> ints = new java.util.ArrayList<>(java.util.List.of(1, 2, 3));
// List<Number> nums = ints;            // does NOT compile -- generics are invariant
// nums.add(3.14);                       // ...which is what invariance prevents

Wildcards let a method parameter or variable opt into a safe, one-directional subtyping relationship — use-site variance (Java has no declaration-site variance; you cannot make List<E> itself covariant):

  • Covariance with ? extends T: List<Integer> is a List<? extends Number>. The generic type varies the same direction as its argument (subtype in → subtype out). You may read elements as T, but may not add any (the compiler cannot prove your value fits the unknown element type). Use it for a source you only pull values from.

  • Contravariance with ? super T: List<Number> is a List<? super Integer>. The generic type varies the opposite direction (subtype in → supertype out). You may add T and its subtypes; reads come back only as Object. Use it for a sink you only push values into.

  • Invariance — a plain List<T> — when a parameter is both read from and written to as T.

List<Integer> ints = java.util.List.of(1, 2, 3);
List<? extends Number> covariant = ints;                  // OK -- covariance
Number n = covariant.get(0);                              // read: fine
// covariant.add(1);                                      // rejected: cannot write

List<? super Integer> contravariant = new java.util.ArrayList<Number>();  // OK -- contravariance
contravariant.add(42);                                    // write: fine
Object o = contravariant.get(0);                          // read: only as Object

Arrays, by contrast, are covariant and reified: Integer[] is an Object[], but a bad store is caught only at runtime as ArrayStoreException. Generics trade that late failure for a compile-time error — one reason not to mix arrays and generics (see Type Erasure).

Wildcards and PECS

A wildcard ? is an unknown type argument, used where a named type parameter would add nothing. List<?> means "a list of some specific but unknown type" — distinct from List<Object>, a list that genuinely accepts any element. See Wildcards.

An upper-bounded wildcard ? extends Number is the covariant case (a producer); a lower-bounded wildcard ? super Integer is the contravariant case (a consumer), as covered in Variance above.

The practical rule for choosing one is the mnemonic PECS: Producer extends, Consumer super. If a parameter produces T values for you to read, declare it ? extends T; if it consumes T values you supply, declare it ? super T; if it does both, use the exact type T. This is the guidance in Guidelines for Wildcard Use.

import java.util.ArrayList;
import java.util.List;

// src produces Ts; dest consumes Ts -- the shape of java.util.Collections.copy
static <T> void copy(List<? super T> dest, List<? extends T> src) {
    for (int i = 0; i < src.size(); i++) {
        dest.set(i, src.get(i));
    }
}

List<Number> dest = new ArrayList<>(List.of(0, 0, 0));
List<Integer> src = List.of(1, 2, 3);
copy(dest, src);

List<? extends Number> producer = src;
Number first = producer.get(0);          // OK: read as Number
// producer.add(4);                      // does NOT compile: cannot add to a ? extends list

List<? super Integer> consumer = dest;
consumer.add(42);                        // OK: Integer fits the lower bound
Object back = consumer.get(0);           // reads degrade to Object

An unbounded List<?> fits methods that use only Object-level operations (size, clear, isEmpty) or null:

static void printSize(java.util.Collection<?> c) {
    System.out.println(c.size() + " elements");
}

Type Erasure and Its Consequences

Generics are a compile-time feature. The compiler checks types, inserts casts, then erases the type parameters: Box<String> and Box<Integer> share one runtime class Box, an unbounded T becomes Object, and <T extends Number> becomes Number. See Type Erasure and JLS 4.4.

A type is reifiable when its full information survives to runtime: primitives, non-generic types, raw types, unbounded wildcards like List<?>, and arrays of those. A parameterised type such as List<String> is non-reifiable. Arrays are reified and perform a runtime store check; generic collections are not — which is why the two mix badly.

import java.util.List;

class Consequences<T> {

    // T[] array = new T[10];             // ERROR: generic array creation
    @SuppressWarnings("unchecked")
    T[] array = (T[]) new Object[10];     // the usual workaround; unchecked

    void checks(Object o) {
        // if (o instanceof T) { }        // ERROR: illegal generic type for instanceof
        // Class<T> c = T.class;          // ERROR: cannot use the type parameter as a class literal
        if (o instanceof List<?> list) { // only the unbounded wildcard form is allowed
            System.out.println(list.size());
        }
    }

    // void m(List<String> ls) { }
    // void m(List<Integer> li) { }       // ERROR: both erase to m(List) -- name clash
}

Because the runtime cannot check element types, an unchecked cast can slip the wrong object into a collection — heap pollution:

List<String> strings = new java.util.ArrayList<>();
List raw = strings;              // raw type: unchecked-warning territory
raw.add(42);                     // heap pollution: an Integer now sits in a List<String>
String oops = strings.get(0);    // ClassCastException here, far from the real mistake

Bridge methods are synthetic methods the compiler adds so overriding still works after erasure. When class IntBox implements Comparable<IntBox> defines compareTo(IntBox), the compiler also emits compareTo(Object), which casts and delegates, satisfying the erased Comparable interface. You never write them, but they appear in stack traces and reflection output.

A varargs parameter of a non-reifiable type (List<String>...) creates a List<String>[] — an array of a non-reifiable type — so the compiler warns about possible heap pollution at every call site. If the method only reads the varargs array, never stores a wrong value into it, and never leaks it, annotate the method @SafeVarargs to suppress that warning for the method and its callers:

import java.util.ArrayList;
import java.util.List;

@SafeVarargs
static <T> List<T> flatten(List<T>... lists) {
    var out = new ArrayList<T>();
    for (List<T> list : lists) {
        out.addAll(list);           // read-only use: safe
    }
    return out;
}

See Also

  • Interfaces — Comparable<T> and Comparator<T>, the archetypal generic interfaces.

  • Collections Framework — the most generics-heavy API in the standard library.

  • Methods and Parameters — overload resolution, which erasure constrains.

  • Arrays — reified, covariant arrays and why they clash with generic collections.