Java Generics

Write classes and methods that work with any type safely, using type parameters, generic methods, bounded types and wildcards.

Lesson 62 of 127Advanced17 min read

Every time you write List<String> or Map<String, Integer>, you're using generics. The type in angle brackets tells Java what the collection holds, so the compiler can stop you adding a number to a list of names, and you never need to cast what you take out.

Before generics arrived in Java 5, a list could hold anything, and programs were full of casts like (String) list.get(0) that crashed at run time when the wrong type had slipped in. Generics moved those errors to compile time, where they're cheap to fix.

In this lesson you'll go from using generic classes to writing your own: a generic class with a type parameter, generic methods, bounds that limit which types are allowed, and the wildcards ? extends and ? super that you'll see in Java's own library code. You'll also learn about type erasure, which explains a few of generics' quirks.

Why generics?

Java
List names = new ArrayList();          // raw type: holds anything
names.add("Ana");
names.add(42);                         // compiles!
String first = (String) names.get(1);  // ClassCastException at run time

List<String> safe = new ArrayList<>();
safe.add(42);                          // compile error: caught early
  • List<String> reads as "a list of String".
  • The empty <> on the right, the diamond, tells Java to work out the type from the left side.
  • Generics only work with object types, so use Integer, Double and Character rather than int, double and char. Java converts between them automatically.

Writing a generic class

Add a type parameter in angle brackets after the class name, and use it like a type inside:

Java
class Box<T> {
    private final T item;
    Box(T item) { this.item = item; }
    T get() { return item; }
}

Box<String> b = new Box<>("Hello");
String s = b.get();       // no cast needed

T is a placeholder that's filled in when the class is used. By convention, type parameters are single capital letters: T for type, E for element, K and V for key and value. A class can have several, such as Pair<A, B>.

Generic methods and bounds

A method can have its own type parameter, written before the return type:

Java
static <T> T firstOrDefault(List<T> list, T fallback) {
    return list.isEmpty() ? fallback : list.get(0);
}

Java infers T from the arguments, so you just call firstOrDefault(names, "none").

A bound limits which types are allowed. <T extends Comparable<T>> means "any type that can be compared with itself", which lets the method call compareTo. <T extends Number> allows Integer, Double and friends, and lets you call doubleValue().

Wildcards

A List<Integer> is not a List<Number>, even though Integer is a Number. Otherwise you could add a Double to a list of Integers through the Number reference. Wildcards relax this safely:

WildcardMeansYou can
List<?>A list of some unknown typeRead items as Object
List<? extends Number>Number or any subtypeRead items as Number
List<? super Integer>Integer or any supertypeAdd Integers

A memory aid often quoted from the book Effective Java: "producer extends, consumer super". Use extends when a parameter gives you values, and super when it receives them.

Java Generics Syntax

Generics syntax

class Name<T> { T value; }
class Pair<A, B> { A first; B second; }
static <T> void method(T x) { }
static <T extends Comparable<T>> T max(List<T> list) { }
double sum(List<? extends Number> nums) { }
void fill(List<? super Integer> out) { }
Name<String> n = new Name<>();

Java Generics Summary

  • Generics put types in angle brackets so the compiler catches type errors early.
  • Write generic classes with type parameters like class Box<T>; the diamond <> infers types.
  • Generic methods declare their own type parameter before the return type: static <T> T f(...).
  • Bounds such as <T extends Comparable<T>> restrict the allowed types.
  • ? extends is for reading, ? super is for adding; types are erased at run time.

Java Generics Examples

Each example shows the code, the output it produces and what each part does.

Example 1

A generic Pair and a generic method

Java
import java.util.List;

public class Main {
    record Pair<A, B>(A first, B second) {}

    static <T> T lastOrDefault(List<T> list, T fallback) {
        return list.isEmpty() ? fallback : list.get(list.size() - 1);
    }

    public static void main(String[] args) {
        Pair<String, Integer> score = new Pair<>("Ana", 120);
        Pair<String, Boolean> flag = new Pair<>("darkMode", true);
        System.out.println(score.first() + " scored " + score.second());
        System.out.println(flag);

        System.out.println(lastOrDefault(List.of("tea", "coffee"), "water"));
        System.out.println(lastOrDefault(List.<String>of(), "water"));
        System.out.println(lastOrDefault(List.of(3, 7, 9), 0) + 1);
    }
}
Output
Ana scored 120
Pair[first=darkMode, second=true]
coffee
water
10
Line-by-line explanation
  1. Line 4Records can be generic too: one Pair works for any two types.
  2. Lines 6 to 8<T> before the return type makes this a generic method.
  3. Lines 11 to 12The same record holds a String and Integer, then a String and Boolean.
  4. Line 18T is Integer here, so the result can be used in arithmetic without a cast.
Example 2

A bounded type: the largest item

Java
import java.util.List;

public class Main {
    static <T extends Comparable<T>> T largest(List<T> items) {
        T best = items.get(0);
        for (T item : items) {
            if (item.compareTo(best) > 0) {
                best = item;
            }
        }
        return best;
    }

    public static void main(String[] args) {
        System.out.println(largest(List.of(4, 19, 7)));
        System.out.println(largest(List.of("pear", "apple", "plum")));
        System.out.println(largest(List.of(2.5, 1.5)));
    }
}
Output
19
plum
2.5
Line-by-line explanation
  1. Line 4T must be Comparable, which guarantees that compareTo exists.
  2. Line 7compareTo returns a positive number when item is larger than best.
  3. Lines 15 to 17The same method works for Integers, Strings (alphabetical order) and Doubles.
Example 3

Wildcards for reading and writing

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

public class Main {
    static double total(List<? extends Number> numbers) {
        double sum = 0;
        for (Number n : numbers) {
            sum += n.doubleValue();
        }
        return sum;
    }

    static void addCountdown(List<? super Integer> out, int from) {
        for (int i = from; i > 0; i--) {
            out.add(i);
        }
    }

    public static void main(String[] args) {
        System.out.println(total(List.of(1, 2, 3)));
        System.out.println(total(List.of(1.5, 2.25)));

        List<Number> mixed = new ArrayList<>(List.of(0.5));
        addCountdown(mixed, 3);
        System.out.println(mixed + " sums to " + total(mixed));
    }
}
Output
6.0
3.75
[0.5, 3, 2, 1] sums to 6.5
Line-by-line explanation
  1. Line 5? extends Number accepts a List<Integer>, List<Double> and so on, for reading.
  2. Line 13? super Integer accepts any list that can hold Integers, such as List<Number> or List<Object>.
  3. Lines 23 to 24Integers are added to a list of Numbers, then the same list is totalled.

Common Java Generics Mistakes

Using raw types

Don't
List items = new ArrayList();
items.add("x");
String s = (String) items.get(0);
Do
List<String> items = new ArrayList<>();
items.add("x");
String s = items.get(0);

Primitive types in angle brackets

Don't
List<int> ages = new ArrayList<>();
Do
List<Integer> ages = new ArrayList<>();

Expecting List<Integer> to be a List<Number>

Don't
static double total(List<Number> nums) { ... }
total(List.of(1, 2, 3));        // compile error
Do
static double total(List<? extends Number> nums) { ... }

Java Generics in Real Projects

Generics in practice

The whole collections framework, Optional<T>, Stream<T> and CompletableFuture<T> are generic. Application code uses generics for repositories like Repository<User>, API wrappers like Response<T> and caches like Cache<K, V>, so one well-tested class serves many types. Reading method signatures with bounds and wildcards becomes much easier once you've written a few yourself.

Java Generics FAQ

What does T mean in Java generics?

T is a type parameter, a placeholder for a real type that's supplied when the class or method is used. In Box<String>, T becomes String. The single letter is only a convention; E for element, K and V for key and value, and R for result are also common.

What is the difference between List<?> and List<Object>?

List<Object> is a list that can hold any object, and you can add anything to it. List<?> is a list of some specific but unknown type: you can read items as Object, but you can't add anything except null. A method taking List<?> accepts a List<String>, but one taking List<Object> doesn't.

Why can't I use int with generics?

Type parameters must be object types, because generic code is compiled once to work with Object references. Use the wrapper types, such as List<Integer>; autoboxing converts values automatically. Primitive-specialised streams such as IntStream exist for heavy numeric work.

Can I create a generic array like new T[10]?

No. Because of type erasure, Java doesn't know what T is at run time, so it can't create an array of it. Use a List<T> instead, or create an Object[] and cast it inside the class, which is how ArrayList works internally.

Can I check a generic type at run time?

Not directly. list instanceof List<String> doesn't compile, because the String part is erased. You can check list instanceof List<?> and then check individual elements, or pass a Class<T> object into the method when it really needs the type at run time.

Java Generics Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the declaration of a generic class with one type parameter.

class Box<> {
    private T item;
}
Multiple choiceeasy

What does the diamond <> in new ArrayList<>() do?

Multiple choicemedium

Where does a generic method declare its type parameter?

Predict the outputmedium

What does largest(List.of("kiwi", "banana", "fig")) return, using the lesson's method?

Multiple choicehard

Which parameter type lets a method add Integers to a List<Object>?

Java Generics Quiz

5 questions to check what you learned.

  1. What problem do generics solve?

    • Slow loops
    • Type errors that would otherwise only appear at run time, plus the need for casts
    • Memory leaks
    • Formatting numbers
  2. Why can't you write List<double>?

    • double is too large
    • Lists can't hold numbers
    • Generics only work with object types; use Double
    • You need a cast
  3. What does <T extends Number> mean?

    • T can be Number or any subclass, such as Integer or Double
    • T must be exactly Number
    • T extends the Number class at run time
    • T can be any type
  4. What does "producer extends, consumer super" describe?

    • Inheritance rules
    • Thread safety
    • Constructor order
    • When to use ? extends (reading) and ? super (writing) wildcards
  5. Why can't you write new T() inside a generic class?

    • T is always abstract
    • Constructors can't be generic
    • Type erasure removes T at run time, so Java doesn't know which class to create
    • It's only allowed in records