skip to content
everything.generics.boundedTypes

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.

don'tunbounded: T is just an Object
java
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)
dobounded: T is promised to be Comparable
java
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:

public static void sort()
1 / 6

<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.

read it out loudsort a list of Ts, where a T knows how to compare itself with Ts, or with something T inherits from.
click a piece, or press next piece. the amber part is the one being explained. this is jdk 25's real signature, from 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:

List of
final class LocalDate implements ChronoLocalDate, ...interface ChronoLocalDate extends Comparable<ChronoLocalDate>, ...
<T extends Comparable<T>> T max(List<T> xs)
compile error
error: method max in class Main cannot be
  applied to given types;
  reason: inference variable T has incompatible
    equality constraints ChronoLocalDate,LocalDate
<T extends Comparable<? super T>> T max(List<T> xs)
compiles

LocalDate 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.

pick an element type. the top box shows how that type gets its 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.

Main.java
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)));
}
output
pear
2026-09-15
6.0 1.5
predict the output
java
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>
T must 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 Number after Comparable<T> and javac says interface expected here.
erasure picks the first
at run time T is replaced by its first bound. that detail matters more than it looks, see the deep dive below.
one level deeper:why Collections.max says <T extends Object & Comparable<? super T>>+

in the JDK source, Collections.max is declared like this:

java
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.

one level deeper: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.

keylines
  • a bound limits what callers can pick, and unlocks methods your code can call.
  • extends in a bound means subtype: classes and interfaces alike.
  • T extends Comparable<T>: a recursive bound, "comparable to itself".
  • add ? super T so types that inherit compareTo (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. super is for wildcards.