JavaBounded Type Parameters

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

Java
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

Java
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

Java
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;
}
Note
Bounding a type parameter narrows what callers can pass in, but widens what you can do with it inside the method or class — that trade-off is the whole point. An unbounded `T` is maximally flexible for callers but gives you almost nothing to work with beyond `Object`'s methods.
  • <T extends Type> restricts a generic type parameter to Type or its subtypes.

  • extends is 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.