Java Bounded Type Parameters
Constrain Java generic types with bounded type parameters using extends — single and multiple bounds.
Java Bounded Type Parameters
A bounded type parameter is a type parameter with an extends clause that restricts what types can fill it in. Without a bound, T is treated as Object — the compiler has no way to know whether T has a compareTo or an intValue or any other method. With a bound, you make a promise to the compiler about what T is, and in exchange the compiler lets you call the methods that promise implies. Almost every interesting generic method has at least one bound somewhere in its signature.
The basic shape: <T extends Bound>
The syntax is extends between the parameter and its bound — same keyword whether the bound is a class or an interface:
public static <T extends Number> double sum(List<T> values) {
double total = 0;
for (T n : values) total += n.doubleValue(); // legal — T is-a Number
return total;
}Read <T extends Number> as "any T that is a Number or a subclass of one." Inside the body, you can now treat any T value as a Number — call doubleValue(), intValue(), anything in Number's public API.
Use it the same way as any other generic method:
sum(List.of(1, 2, 3)); // T = Integer — fine
sum(List.of(1.5, 2.5)); // T = Double — fine
sum(List.of(1L, 2L, 3L)); // T = Long — fine
sum(List.of("a", "b")); // ❌ String is not a NumberWithout the bound, sum couldn't even try to call doubleValue() — T would be erased to Object, and Object doesn't have that method. The bound is what makes the body legal.
extends works for interfaces too
In Java generics, extends is overloaded — it means "is a subtype of," whether the bound is a class or an interface. There's no separate implements keyword in a type parameter list:
public static <T extends Comparable<T>> T max(List<T> list) {
T best = list.get(0);
for (T candidate : list) {
if (candidate.compareTo(best) > 0) best = candidate;
}
return best;
}
max(List.of(3, 1, 4, 1, 5, 9)); // T = Integer
max(List.of("Ada", "Grace", "Linus")); // T = StringComparable<T> is an interface, but the syntax is still extends. Read <T extends Comparable<T>> as "any T that is comparable to itself" — the constraint is what lets you call candidate.compareTo(best) inside the loop.
The little wrinkle here is the <T> inside the bound — it's the self-referential form we met in generic interfaces. It enforces that compareTo accepts a T, not just any Comparable. Without it, you could compare Integer to String and the type system wouldn't notice.
Multiple bounds
A type parameter can have more than one bound, joined by &:
public static <T extends Number & Comparable<T>> T maxNumber(List<T> list) {
T best = list.get(0);
for (T n : list) {
if (n.compareTo(best) > 0) best = n;
}
return best;
}Now T must be both a Number and a Comparable<T>. Inside the body you can call any method from either. Integer, Long, Double, BigDecimal all satisfy both — you can pass any of them.
A few rules:
- The class bound (if any) must come first.
<T extends Number & Comparable<T>>is legal;<T extends Comparable<T> & Number>is not. - At most one class bound. Java has single inheritance for classes, so this falls out of the language's existing rules.
- Any number of interface bounds. Stack as many as you genuinely need; in practice, two is the usual maximum before the design needs a rethink.
The class-first rule reflects the bytecode: erasure replaces T with its leftmost bound, so the leftmost slot is special. We'll come back to that in Type Erasure.
Bounds on class-level type parameters
Bounds aren't unique to methods. A generic class or interface can bound its type parameters in the declaration:
public class SortedBag<T extends Comparable<T>> {
private final List<T> items = new ArrayList<>();
public void add(T item) {
items.add(item);
Collections.sort(items);
}
public T smallest() { return items.get(0); }
}
SortedBag<Integer> bag = new SortedBag<>(); // OK — Integer is Comparable<Integer>
// SortedBag<Object> bag2 = new SortedBag<>(); // ❌ Object is not Comparable<Object>This is the right place for a bound that applies to the whole class. Every method, every field, every default operation has access to the constraint.
Lower bounds belong to wildcards, not type parameters
A common confusion: type parameters can have upper bounds (extends) but not lower bounds (super). This is legal:
public static <T extends Number> void foo(T x) { ... }This is not:
public static <T super Integer> void bar(T x) { ... } // ❌ no such syntaxLower bounds do exist in Java's generics — but they only live on wildcards, the ? token, not on named type parameters. We'll cover the full ? extends / ? super story in the Wildcards chapter; for now, just know that super works on ? and not on T.
When to add a bound
The default should be "no bound" — the looser the constraint, the more types your method accepts. Add one when, and only when, the body needs to call a specific method on T:
- "I need to compare
Tvalues" →<T extends Comparable<T>>. - "I need to add
Tvalues as numbers" →<T extends Number>. - "I need to read fields off
Tlike aUser" →<T extends User>. - "I need
Tto be auto-closeable so I can use it in try-with-resources" →<T extends AutoCloseable>.
If you're not calling a method on T, you don't need a bound — and adding one anyway just restricts your API for no reason.
A worked example: a bounded between and a sorted top
Two methods, each with a different style of bound. between uses Comparable<T> so it can clamp; topN uses Number & Comparable<T> so it can both compare and report a numeric sum.
between accepts any Comparable — including Character, which is Comparable<Character> and has nothing to do with numbers. topN is narrower — it needs both ordering (to sort) and numeric values (to sum), so it stacks the two bounds with &. Each method asks for exactly the capability it uses, no more.
What's next
A bound on a type parameter pins down a single type at the call site — List<Integer> means "this list, this method, this type." Sometimes you want to be less specific: "a list of some Number — could be Integer, could be Double, I don't care." That's what wildcards are for, and they fix one of the biggest sources of confusion in Java's type system. Continue to Java Generic Wildcards.
Practice
You need a method that finds the largest element of a list. Which signature lets you call `compareTo` on the elements without restricting callers more than necessary?