AlgoMaster Logo

Bounded Type Parameters

Medium Priority20 min readUpdated September 21, 2026
Listen to this chapter
Unlock Audio

A plain type parameter like <T> is freedom with a price. T can be any reference type, which sounds powerful, but inside the method or class you only get to call methods that exist on Object. You can't ask T for its price, its rating, or whether it compares less than some other T. Bounded type parameters fix that by saying "T can be anything, as long as it's at least a Product," or "T can be anything, as long as it's Comparable to itself." This lesson covers single bounds with extends, multiple bounds, recursive bounds, and how bounds change what you can do inside a generic body.

Why Unbounded Generics Are Limiting

Consider a tiny utility class that picks the cheapest item from a list. With an unbounded type parameter, the body has nothing to work with:

This doesn't compile. The error is direct:

From the compiler's view, T could be any reference type. String doesn't have getPrice. Neither does Integer, or URL, or any class you might invent next week. With no constraint on T, the only methods the compiler trusts are the ones on Object: equals, hashCode, toString, and a few more.

The fix is to constrain T to types that do have getPrice. Bounded type parameters do exactly that: they narrow the universe of allowed type arguments so the body has methods to call.

Single Upper Bound With extends

The syntax is <T extends SomeType>. It says "any type argument for T must be SomeType or a subtype of it." The extends keyword here doesn't mean class inheritance specifically; it means "is a subtype of," which covers both class inheritance and interface implementation. There is no implements keyword in generic bounds. Whether SomeType is a class or an interface, you still write extends.

Here's a fixed CheapestPicker with T bounded to Product:

The body of pickCheapest now calls item.getPrice() freely. The compiler is convinced that any T plugged into this class is a Product (or one of its subclasses), so every Product method is fair game.

A bound on T is a promise about what T supports. Inside the method, the compiler treats T as if it were that bound. From the outside, callers see the more precise type they passed in.

Adding a bound doesn't add runtime overhead. The bound is checked at compile time, and at runtime T is erased to its bound, not to Object. A <T extends Product> parameter erases to Product, which means fewer casts inserted by the compiler.

Bounds Work With Subtypes

A bound admits the bound type and everything below it. If DigitalProduct and PhysicalProduct both extend Product, then a CheapestPicker<DigitalProduct> and a CheapestPicker<PhysicalProduct> are both legal. A CheapestPicker<String> is not.

The call site uses DigitalProduct as the type argument and gets back a DigitalProduct, not a Product. The bound is the floor on what T can be, but the type argument can be any subtype, and the return type tracks it. That's the second reason bounds beat using Product directly as the parameter and return type: you don't lose type information about subclasses.

For <T extends Number>, the green nodes under Number are allowed: Integer, Double, Long. String is not. For <T extends Product>, the green nodes under Product are allowed: DigitalProduct, PhysicalProduct. A class higher up like Object is not, because Object is a supertype of Product, not a subtype.

Bounds On Interfaces

Bounds with extends accept interfaces just as readily as classes. A bound like <T extends Comparable<T>> says "T must be a type that knows how to compare itself to other instances of T." Inside the body, you can call compareTo on any T.

Integer and String both implement Comparable, so both are accepted as type arguments. The body calls item.compareTo(best) without any cast or instanceof check, because the bound has already promised that T supports it.

The extends keyword reads strangely when the right-hand side is an interface. English-language intuition says you "implement" an interface and "extend" a class. Generic bounds smooth over the distinction. The Java designers picked one word for both cases so the syntax doesn't fork. Whenever you see <T extends X>, read it as "T is a subtype of X," whether X is a class or an interface.

Calling Methods On Bounded T

The rule is simple. Inside a class or method whose type parameter is bounded by B, every method available on B is available on any value of type T. No casts. No checks. If B defines getPrice(), then someT.getPrice() compiles. If B is Comparable<T>, then someT.compareTo(other) compiles.

A short example with a class bound for context. Cart<T extends Product> can compute totals because it knows every T has getPrice:

The body of total calls item.getPrice() on each T. Without the bound, that call wouldn't compile. With the bound, it's as natural as if the field were typed Product directly. The extra value of the bound is that Cart<DigitalProduct> keeps the precise element type, which matters when you read items back out.

Multiple Bounds

A type parameter can require more than one supertype. The syntax uses & to separate the bounds:

This says "T must be a subtype of all of these." The rules are strict:

  • If a class is among the bounds, it must come first. You can list at most one class.
  • Everything after the first bound must be an interface.
  • Bounds are separated by &, not commas (commas separate distinct type parameters).

The standard use case is "T must be a Product and also Comparable." Then the body can both ask T for its price and ask one T to compare itself to another.

SortedCatalog requires both extends Product (so it can read prices) and Comparable<T> (so it can rank items). RatedProduct satisfies both: it's a Product, and it implements Comparable<RatedProduct>. Plug in any other type and the compiler rejects it.

Try swapping the order, and the compiler complains:

The class bound must come first. Everything after the first & is interpreted as an interface, and Product is a class. Swap them and the code compiles. This rule exists because Java has single inheritance for classes: a type can extend only one class but implement many interfaces, so at most one of the bounds can be a class, and putting it first makes that explicit.

Recursive (Self-Referential) Bounds

A recursive bound is one where T appears in its own constraint. The most common is <T extends Comparable<T>>. Read it as "T must be a type that knows how to compare itself to other Ts." This pattern shows up everywhere in the JDK because it's a clear way to say "items in this collection can be ordered against each other."

Here's the pattern as a generic method:

The bound T extends Comparable<T> is what makes item.compareTo(best) legal inside the method. Without the recursive bound, item.compareTo(...) wouldn't type-check, because the compiler would have no idea what T is comparable against.

The JDK uses a useful variant for Collections.max:

The Comparable<? super T> part allows a type that's comparable to any of its supertypes, not strictly to itself. That lets you call max on a List<DigitalProduct> when the comparison logic lives in Product. The takeaway here is that the recursive bound is the structural idea, and the wildcard is a refinement that buys flexibility.

The pattern works because every type that implements Comparable<Self> satisfies the bound at the call site, and inside the method the compiler can rely on compareTo being there.

Bounds On Classes, Methods, And Interfaces

Bounds aren't limited to one shape. The same syntax works on generic classes, generic methods, and generic interfaces. Here's a quick example of each.

A generic class with a bound:

A generic method with a bound (the method is generic even though its enclosing class isn't):

A generic interface with a bound:

The bound's effect is the same in every case: inside the body, the compiler treats T as if it were the bound type. The body of averagePrice calls getPrice on each T. An implementation of Ranker can call compareTo on any T. A PriceSorter<T extends Product> can sort items by price because every T is a Product.

What you can't do is escape the bound. A PriceSorter<String> is rejected before the body ever runs. The bound is enforced at every call to the constructor, every call to the method, and every assignment to a variable typed PriceSorter<...>.

A bounded T erases to its bound, not to Object. For <T extends Product>, the compiled bytecode treats T as Product, which means fewer synthetic casts when callers read the result. For <T> with no bound, erasure goes to Object, and the compiler inserts casts at every read. The runtime cost difference is usually negligible, but it's there.

Bounds vs Wildcards

The lesson next door is about wildcards (<? extends Product>, <? super Product>). The two ideas look similar but live on opposite sides of a declaration.

FeatureBounded type parameterWildcard
Where it appearsOn a class/method declaration: <T extends Product>On a use of a generic type: List<? extends Product>
What it namesA type variable you can refer to elsewhere in the signatureAn anonymous type position
Use case"I need to know this type inside the body and possibly return it""I just want to consume or produce values without naming the type"
Reusable nameYes (the same T can appear in multiple places)No (each ? is independent)

A bounded type parameter is for declaring a generic class or method. A wildcard is for using a generic type with flexibility about its argument. They cover different problems.

Common Mistakes

A short list of common bugs:

Using `implements` instead of `extends` for an interface bound.

In generic bounds, you always write extends, even when the bound is an interface.

Putting a class after an interface in multiple bounds.

The class must come first. Swap to <T extends Product & Comparable<T>>.

Listing more than one class in the bounds.

If both Product and Order are classes, this is rejected. Java's single inheritance for classes means at most one class can be in the bounds.

Expecting the bound to grant access to subclass methods.

This fails because getFileSizeMb lives on DigitalProduct, not on Product. The bound only promises Product's API. Even if every caller passes DigitalProduct, the compiler binds the body to the bound, not to whatever specific subtype shows up later.

A Worked End-to-End Example

A small program that ties it all together: bounded class, bounded method, multiple bounds, recursive bound. The shop has a wishlist that can only hold Product subtypes, and it can rank items by their natural order when those items happen to be Comparable.

Wishlist requires two things from its type argument: that it's a Product (so it can read prices) and that it's Comparable to itself (so it can rank). RatedProduct satisfies both, and the wishlist works smoothly. Catalog.max is a generic method with a recursive bound, and it works on any Comparable type, from String to RatedProduct. The same pattern, three different scopes.

Quiz

Bounded Type Parameters Quiz

10 quizzes