bounded type parameters
<T extends Comparable<? super T>> decoded.
- chapter
- chapter 05.03
- reading time
- 7 min read
a bound limits what a type parameter can be: <T extends Number> means callers may only pick Number or a subtype. in return, your code gets to call Number's methods on a T. that's the trade.
the famous one, <T extends Comparable<? super T>>, reads: "a T that can be compared with other Ts, or with anything T inherits from". by the end of this page it'll look boring.
the picture
you know how a plain <T> can be anything, which sounds powerful until you try to do something with it? inside the method, javac only knows T is some object, so all you can call is equals, hashCode, toString and the rest of Object. ask a T for compareTo and javac says cannot find symbol.
think of a job posting. "anyone can apply" gets you lots of applicants and no guarantees. "must hold a driving licence" turns some people away, but now you can hand any applicant the van keys. a bound is the requirement in the posting: callers have to meet it, and your code gets to rely on it.
static <T> T max(List<T> xs) {
T best = xs.get(0);
for (T x : xs)
if (x.compareTo(best) > 0)
best = x;
return best;
}
// error: cannot find symbol
// symbol: method compareTo(T)static <T extends Comparable<T>>
T max(List<T> xs) {
T best = xs.get(0);
for (T x : xs)
if (x.compareTo(best) > 0)
best = x;
return best;
}extends in a bound means "is a subtype of", for classes and interfaces. there's no implements here: <T implements Comparable<T>> isn't java.
decoding the scary one
here's the real signature of Collections.sort. it looks like line noise until you read it in pieces:
<Tthis method declares its own type parameter, T. it's a generic method, so T is worked out fresh at every call: sort a List<String> and T is String.
javap -s java.util.Collections.the first half, T extends Comparable<T>, is a recursive bound: T shows up inside its own bound. it reads "a T that can be compared to other Ts", which is exactly what String (implements Comparable<String>) and Integer are. it's how a method says "these things have a natural order" without knowing what they are.
so why the ? super T? because plenty of types inherit their compareTo from a parent, and then they're comparable to the parent, not to themselves.
why ? super T
max again, written both ways, called with five different lists:
error: method max in class Main cannot be
applied to given types;
reason: inference variable T has incompatible
equality constraints ChronoLocalDate,LocalDateLocalDate gets compareTo from its parent interface, so it's a Comparable<ChronoLocalDate>, not a Comparable<LocalDate>. the strict bound wants T to be both LocalDate and ChronoLocalDate. ? super T accepts a T that compares with a supertype.
compareTo. the two boxes below are the same method with the strict bound and the loose one, and what javac 25 says about each call: compiles or doesn't.LocalDate is the one that bites people. it's Comparable<ChronoLocalDate>, because compareTo is declared once, up in the ChronoLocalDate interface, for every calendar system. so Comparable<T> with T = LocalDate fails, and Comparable<? super T> (comparable to LocalDate or any supertype of it) passes. the same thing happens to your own classes the moment a subclass inherits compareTo. that's why the JDK writes ? super T on every Comparable and Comparator it accepts, and you should too. the rule behind it, producer extends and consumer super, gets its own chapter: wildcards and PECS.
static <T extends Comparable<? super T>> T max(List<T> xs) {
T best = xs.get(0);
for (T x : xs) if (x.compareTo(best) > 0) best = x; // compareTo: T is Comparable
return best;
}
static <T extends Number & Comparable<T>> double range(List<T> xs) {
T lo = xs.get(0), hi = xs.get(0);
for (T x : xs) {
if (x.compareTo(lo) < 0) lo = x; // from Comparable
if (x.compareTo(hi) > 0) hi = x;
}
return hi.doubleValue() - lo.doubleValue(); // from Number
}
void main() {
IO.println(max(List.of("pear", "apple", "fig")));
IO.println(max(List.of(LocalDate.of(2025, 9, 16), LocalDate.of(2026, 9, 15))));
IO.println(range(List.of(3, 9, 4)) + " " + range(List.of(2.5, 1.0)));
}pear 2026-09-15 6.0 1.5
static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
IO.println(max(1, 2.5));more than one bound
that range method above wanted two things from T: compareTo and doubleValue. so it asked for both, with &:
<T extends A & B & C>Tmust be a subtype of all of them. inside the method you can call methods from any of them.- class first
- at most one of them can be a class, and it has to come first. put
NumberafterComparable<T>and javac saysinterface expected here. - erasure picks the first
- at run time
Tis replaced by its first bound. that detail matters more than it looks, see the deep dive below.
why Collections.max says <T extends Object & Comparable<? super T>>+
in the JDK source, Collections.max is declared like this:
public static <T extends Object & Comparable<? super T>>
T max(Collection<? extends T> coll)extends Object looks pointless, every type extends Object. it's there for erasure: a type parameter is erased to its first bound. with just Comparable<? super T>, the compiled method would return Comparable. but max existed before generics, returning Object, and old class files call it by that exact descriptor. putting Object first keeps the erased signature identical. you can see it with javap -s: Collections.max has descriptor (Ljava/util/Collection;)Ljava/lang/Object;, while a max you write with only the Comparable bound compiles to (Ljava/util/List;)Ljava/lang/Comparable;. generic source, 1998-compatible bytecode.
the strangest bound in the JDK: Enum<E extends Enum<E>>+
every enum secretly extends java.lang.Enum<E extends Enum<E>>. so enum Color is really class Color extends Enum<Color>. the recursion lets the base class talk about the exact subclass: compareTo(E o) only accepts other Colors, and getDeclaringClass() returns a Class<Color>. a plain Enum base class couldn't stop you comparing a Color with a Planet. same trick, called "f-bounded" or the curiously recurring pattern, shows up in builder hierarchies. more in enums.
- a bound limits what callers can pick, and unlocks methods your code can call.
extendsin a bound means subtype: classes and interfaces alike.T extends Comparable<T>: a recursive bound, "comparable to itself".- add
? super Tso types that inheritcompareTo(LocalDate, subclasses) still fit. <T extends A & B>: class first, at most one class. erasure uses the first bound.- type parameters only have upper bounds.
superis for wildcards.