Bounded Type Parameters
By default, a generic type parameter like T can stand in for any reference type — there is no restriction on what T might be. Most of the time that is exactly what you want, but sometimes a generic class or method needs to call methods that only exist on certain types. Bounded type parameters let you restrict T to a specific type or its subtypes, using the extends keyword.
Restricting a type parameter with extends
Writing <T extends Number> restricts T to Number or any of its subclasses — Integer, Double, Long, and so on. Note that extends is used here even when the bound is an interface; in generics, extends always means “is-a” regardless of whether the bound is a class or an interface.
The real payoff is that inside the generic class or method, the compiler now knows T has all of Number's methods available — like doubleValue() — so you can call them directly. Without the bound, T would be treated as a plain Object, and Number-specific methods would not compile.
A bounded generic class
public class NumericBox<T extends Number> {
private T value;
public NumericBox(T value) {
this.value = value;
}
public double asDouble() {
// Only possible because T is guaranteed to be a Number
return value.doubleValue();
}
}
public class Main {
public static void main(String[] args) {
NumericBox<Integer> box = new NumericBox<>(42);
System.out.println(box.asDouble()); // 42.0
// NumericBox<String> bad = new NumericBox<>("oops"); // compile-time error
}
}A worked example: a generic max() method
Bounded type parameters are especially common on generic methods that need to compare values. Bounding T to Comparable<T> guarantees every element has a compareTo() method to call.
Finding the maximum of any Comparable type
public class Utils {
public static <T extends Comparable<T>> T max(List<T> items) {
T largest = items.get(0);
for (T item : items) {
if (item.compareTo(largest) > 0) {
largest = item;
}
}
return largest;
}
public static void main(String[] args) {
List<Integer> numbers = List.of(3, 7, 2, 9, 4);
System.out.println(max(numbers)); // 9
List<String> words = List.of("pear", "apple", "kiwi");
System.out.println(max(words)); // pear (lexicographic order)
}
}This single method works for any type that implements Comparable<T> — Integer, String, LocalDate, or any custom class you write that implements the interface — without writing a separate overload for each one.
Multiple bounds
A type parameter can be bounded by more than one type at once, using & to combine them: <T extends Comparable<T> & Serializable> restricts T to types that are both comparable and serializable. If you combine a class bound with interface bounds, the class must come first.
Combining multiple bounds
public static <T extends Comparable<T> & Serializable> T safestMax(List<T> items) {
// T is guaranteed to have both compareTo() and be serializable
T largest = items.get(0);
for (T item : items) {
if (item.compareTo(largest) > 0) {
largest = item;
}
}
return largest;
}<T extends Type>restricts a generic type parameter toTypeor its subtypes.extendsis used for both class and interface bounds in generics.A bound lets you call the bound type's methods directly on values of type
T.<T extends A & B>combines multiple bounds; a class bound (if any) must come first.