Back to Blog
Java

Java Generic Extends Bound: Syntax and Use Cases

java generic extends bound: Learn how the extends bound restricts generic type parameters in Java, with syntax, multiple bounds, wildcard differences, and common pitfa...

Java genericsBounded type parametersType safetyJava programmingGeneric methods
Diagram showing a generic type parameter constrained by an upper bound in Java, with a class hierarchy and type safety.

In Java generics, the extends keyword in a type parameter declaration, such as <T extends Number>, establishes an upper bound on the types that can be used as arguments. This bound tells the compiler that T must be a subtype of the specified class or implement the specified interface. The java generic extends bound is the mechanism that turns a completely open type parameter into one with a known contract, enabling the generic code to call methods defined on the bound without casting.

What the Extends Bound Does in Java Generics

Without a bound, a type parameter T is treated as Object. You can store and return values, but you cannot call any method other than those defined on Object. When you declare <T extends Number>, the compiler knows that T is a subtype of Number, so you can invoke methods like intValue() or doubleValue() directly on a T value. The bound is a compile-time contract; the runtime type is still unknown, but the compiler guarantees that any argument passed will satisfy the bound.

Consider a simple method that returns the sum of two numbers:

public static <T extends Number> double sum(T a, T b) { return a.doubleValue() + b.doubleValue(); }

Here, doubleValue() is available because T is bounded by Number. Without the bound, you would need to cast to Number or use reflection, which is both unsafe and verbose.

Using a Single Upper Bound

A single upper bound is the most common form. You can bound a type parameter by a class or an interface. For example, <T extends Comparable<T>> ensures that T implements the Comparable interface, allowing you to call compareTo:

public static <T extends Comparable<T>> T max(T a, T b) { return a.compareTo(b) >= 0 ? a : b; }

This method works with any type that implements Comparable, such as String, Integer, or a custom class. The bound also allows the compiler to enforce type safety at the call site: passing a non-Comparable type results in a compile-time error.

The bound can also be a class, but a class bound restricts the type to that class or its subclasses. You cannot use a class bound with a primitive type, and you cannot specify both a class and an interface in a single bound without using the ampersand syntax.

Multiple Bounds with the Ampersand

When a type parameter must satisfy more than one constraint, you use the ampersand (&) to separate the bounds. The first bound can be a class or interface; any subsequent bounds must be interfaces. The syntax is <T extends ClassA & InterfaceB & InterfaceC>. For example:

public class Repository<T extends Entity & Identifiable> { public T findById(Long id) { // Implementation that uses both Entity and Identifiable methods return null; } }

Here, T must be a subclass of Entity and must implement Identifiable. This is useful when you need to guarantee both a base implementation and a specific behavior. The order matters: the class must come first, and there can be only one class bound. If you try to put an interface first, the compiler will reject it.

Multiple bounds are often used in generic algorithms that require both a common superclass and a marker interface. However, they increase the coupling between the generic code and the bound types, so they should be used only when the extra constraint is genuinely needed.

Bounded Type Parameters vs. Wildcard Bounds

A common source of confusion is the difference between a bounded type parameter (<T extends Number>) and a wildcard with a bound (? extends Number). The type parameter is used when you need to refer to the same type in multiple places, such as a method that takes two arguments of the same type or returns a value of that type. The wildcard is used when you only need to read values from a collection and do not care about the exact type.

For example, consider a method that sums a list of numbers:

public static double sumList(List<? extends Number> list) { double total = 0; for (Number n : list) { total += n.doubleValue(); } return total; }

Here, ? extends Number allows the method to accept a List<Integer>, List<Double>, or any other list whose element type is a subtype of Number. You cannot add elements to this list because the exact type is unknown, but you can read them as Number. A bounded type parameter would be necessary if you needed to add elements back or if the method required the same type for multiple parameters.

The choice depends on whether the type is used as a "producer" (you read from it) or a "consumer" (you write to it). Wildcards are more flexible for read-only scenarios, while type parameters are necessary when the type must be consistent across the method signature.

Common Compile-Time Errors with Bounds

Misunderstanding bounds leads to several frequent compile-time errors. One is trying to use super as a bound, such as <T super Number>. Java does not support lower bounds on type parameters; super is only valid in wildcards (? super Number). If you need to accept a type that is a supertype of a given type, you must use a wildcard.

Another error is using a primitive type as a bound, like <T extends int>. Bounds must be reference types, so you need to use the wrapper class, such as Integer. Also, you cannot have multiple class bounds: <T extends Number & Integer> is invalid because Integer is a class, not an interface.

A third error occurs when you try to use a bound that is not accessible or is final. If the bound is a final class, then T can only be that exact class, which defeats the purpose of generics. The compiler will allow it, but the generic method becomes effectively non-generic. It is usually better to use an interface or a non-final class as a bound.

Runtime Behavior and Type Erasure

At runtime, Java generics are erased. The compiler replaces type parameters with their leftmost bound, or with Object if no bound is specified. This means that <T extends Number> is compiled as if T were Number. The bound is used for type checking at compile time and for generating the appropriate casts at the bytecode level, but the actual type argument is not retained.

This has practical implications. You cannot use instanceof with a type parameter, and you cannot create an array of a generic type. For example, new T[10] is not allowed because the runtime type is erased. You also cannot catch a generic exception type, because the compiler cannot determine the exact type at runtime.

The bound also affects the generated bridge methods and the way the compiler inserts casts. When you call a method on a bounded type parameter, the compiler inserts a cast to the bound. If the bound is an interface, the cast is to that interface, and the actual implementation is resolved at runtime through the usual virtual dispatch.

Designing APIs with Bounds in Mind

Choosing the right bound is a design decision that affects the flexibility and maintainability of your API. A narrow bound gives you more methods to call but restricts the types that can be used. A wide bound, such as Object, is flexible but forces you to cast and handle type checks manually.

A good rule is to use the least restrictive bound that still provides the functionality you need. If you only need to read a value and call a method from an interface, use that interface as the bound. If you need to store the value in a collection, you might not need a bound at all. Overly specific bounds make the API harder to use and can lead to unnecessary coupling between the generic class and the bound type.

Also consider whether a wildcard would be more appropriate than a type parameter. If the type appears only once in the method signature, a wildcard is often simpler. If it appears multiple times, a type parameter is required to enforce the relationship. For example, public <T extends Number> T max(T a, T b) enforces that both arguments are the same type, while public Number max(Number a, Number b) accepts different subtypes but loses the return type information.

The java generic extends bound is a powerful tool, but it is not always the answer. Knowing when to use a bound, when to use a wildcard, and when to avoid generics altogether will make your code more readable and less error-prone.

java generic extends bound: Practical Usage and Code Example | RYUSLOG DEV