W3docs

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 Number

Without 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 = String

Comparable<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 syntax

Lower 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 T values" → <T extends Comparable<T>>.
  • "I need to add T values as numbers" → <T extends Number>.
  • "I need to read fields off T like a User" → <T extends User>.
  • "I need T to 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.

java— editable, runs on the server

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

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?