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?
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 earlyList<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,DoubleandCharacterrather thanint,doubleandchar. 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:
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 neededT 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:
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:
| Wildcard | Means | You can |
|---|---|---|
List<?> | A list of some unknown type | Read items as Object |
List<? extends Number> | Number or any subtype | Read items as Number |
List<? super Integer> | Integer or any supertype | Add 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. ? extendsis for reading,? superis 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.
A generic Pair and a generic method
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);
}
}Ana scored 120
Pair[first=darkMode, second=true]
coffee
water
10Line-by-line explanation
- Line 4Records can be generic too: one Pair works for any two types.
- Lines 6 to 8<T> before the return type makes this a generic method.
- Lines 11 to 12The same record holds a String and Integer, then a String and Boolean.
- Line 18T is Integer here, so the result can be used in arithmetic without a cast.
A bounded type: the largest item
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)));
}
}19
plum
2.5Line-by-line explanation
- Line 4T must be Comparable, which guarantees that compareTo exists.
- Line 7compareTo returns a positive number when item is larger than best.
- Lines 15 to 17The same method works for Integers, Strings (alphabetical order) and Doubles.
Wildcards for reading and writing
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));
}
}6.0
3.75
[0.5, 3, 2, 1] sums to 6.5Line-by-line explanation
- Line 5? extends Number accepts a List<Integer>, List<Double> and so on, for reading.
- Line 13? super Integer accepts any list that can hold Integers, such as List<Number> or List<Object>.
- Lines 23 to 24Integers are added to a list of Numbers, then the same list is totalled.
Common Java Generics Mistakes
Using raw types
List items = new ArrayList();
items.add("x");
String s = (String) items.get(0);List<String> items = new ArrayList<>();
items.add("x");
String s = items.get(0);Primitive types in angle brackets
List<int> ages = new ArrayList<>();List<Integer> ages = new ArrayList<>();Expecting List<Integer> to be a List<Number>
static double total(List<Number> nums) { ... }
total(List.of(1, 2, 3)); // compile errorstatic 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.
Complete the declaration of a generic class with one type parameter.
class Box<> {
private T item;
}
What does the diamond <> in new ArrayList<>() do?
Where does a generic method declare its type parameter?
What does largest(List.of("kiwi", "banana", "fig")) return, using the lesson's method?
Which parameter type lets a method add Integers to a List<Object>?
Java Generics Quiz
5 questions to check what you learned.
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
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
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
What does "producer extends, consumer super" describe?
- Inheritance rules
- Thread safety
- Constructor order
- When to use ? extends (reading) and ? super (writing) wildcards
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