Back to Blog
Java

Java Bounded Type Parameter: Syntax and Use Cases

java bounded type parameter: Understand Java bounded type parameters: declare bounds, call bound methods, use multiple bounds, and avoid common generic pitfalls.

Java genericstype boundstype safetygeneric methodswildcard boundstype erasure
Illustration of a Java bounded type parameter restricting generic types with a boundary symbol.

A java bounded type parameter restricts the set of types that can be passed to a generic class, interface, or method. The bound is declared with the extends keyword, and it gives the generic code access to the methods of the bound type. This is different from an unbounded type parameter, which can accept any reference type.

Declaring a Bounded Type Parameter

The syntax for a bounded type parameter is straightforward. After the type parameter name, write extends followed by the bound type. The bound can be a class or an interface. For example, the following generic method accepts only types that extend Number and can safely call doubleValue() on each element:

public static <T extends Number> double sum(List<T> numbers) { double total = 0; for (T number : numbers) { total += number.doubleValue(); } return total; }

Here, T is not just a placeholder. It is a type that is guaranteed to be a subtype of Number, so the compiler allows calls to doubleValue(), intValue(), and other methods defined by Number. Without the bound, the code would not compile because the compiler would treat T as Object, which does not declare those methods.

Why Bounds Matter for Method Calls

Consider a generic method that tries to convert a value to a double. Without a bound, the compiler rejects the call to doubleValue():

// Compilation error: cannot find symbol method doubleValue() public static <T> double convert(T value) { return value.doubleValue(); }

The fix is to declare the bound explicitly:

public static <T extends Number> double convert(T value) { return value.doubleValue(); }

This is the primary practical benefit of a bounded type parameter. It lets you write generic logic that depends on the capabilities of the type, while still keeping the implementation reusable across all subtypes of the bound. The compiler enforces the bound at the call site, so passing a String to convert results in a compile-time error rather than a runtime failure.

Multiple Bounds with & Syntax

A type parameter can have multiple bounds. The syntax uses an ampersand between bound types, and the class bound, if any, must come first. All subsequent bounds must be interfaces. For example, to require a type that is both a Number and Comparable, write:

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

This method can be called with Integer, Double, or any other Number type that implements Comparable. The compiler knows that T has both Number methods and compareTo(). If you try to use a class as the second bound, like T extends Number & Integer, the compiler rejects it because a type cannot extend more than one class.

Multiple bounds are useful when a generic algorithm requires both a data operation and a comparison capability. The ampersand syntax is also used in type parameter declarations for generic classes, not just methods.

Bounded Type Parameters vs Wildcard Bounds

A common source of confusion is the difference between a bounded type parameter and a wildcard bound. A bounded type parameter declares a named type variable that can be used in multiple places within the same generic declaration. A wildcard bound, on the other hand, appears only in a specific type argument and is not reusable.

AspectBounded type parameterWildcard bound
Declaration<T extends Number>List<? extends Number>
ReusabilityCan reference T multiple timesEach wildcard occurrence is independent
Method callsCan call bound methods on TCan read elements as Number, cannot add
Typical useGeneric methods and classesFlexible method parameters

For example, a method that processes a list of numbers can use either form. If the method needs to return the same type that it receives, a bounded type parameter is necessary. If the method only needs to read values and the exact type does not matter, a wildcard is simpler:

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

Use a bounded type parameter when the type variable appears in the return type, in multiple parameters, or inside the method body in a way that requires a consistent type. Use a wildcard when the type is only used as an input and the method does not need to preserve the exact type.

Recursive Type Bounds

A recursive type bound is a type parameter that is bounded by a generic type that uses itself. The most common example is <T extends Comparable<T>>, which ensures that T can be compared with another instance of the same type. This pattern appears in generic sorting and searching algorithms:

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

Here, Comparable<T> is the bound, and T appears both as the type parameter and as the argument to Comparable. This tells the compiler that a.compareTo(b) is valid because a is a Comparable<T> and b is a T. Without the recursive bound, the method would have to accept Comparable raw, which loses type safety.

Recursive bounds are also used in class hierarchies, such as class Node<T extends Node<T>>, to allow methods to return the concrete subtype. This pattern is common in builder APIs and fluent interfaces, though it adds complexity and should be used only when it provides real value.

Type Erasure and Runtime Behavior

At runtime, Java erases generic type information. The bound of a type parameter determines its erasure. For a bounded type parameter, the erasure is the leftmost bound. For example, <T extends Number> erases T to Number. This means the compiler inserts casts where necessary, and the runtime sees the bound type rather than the exact type argument.

This behavior has practical consequences. You cannot use a type parameter in an instanceof check, because the exact type is not known at runtime. You also cannot create an array of a type parameter, such as new T[10], because array creation requires a reifiable type. The bound does not make the type reifiable; it only gives the compiler enough information to insert safe casts.

Erasure also affects method signatures. A generic method with a bounded type parameter compiles to a method that uses the bound as the type for the parameter. This is important when overriding generic methods or when using reflection, because the erased signature may not match what you expect from the source code.

Common Mistakes and Edge Cases

Several mistakes appear frequently when developers first use bounded type parameters. One is trying to use a primitive type as a bound, such as T extends int. Bounds must be reference types, so use Integer instead. Another mistake is using multiple class bounds; only the first bound can be a class, and it must be the first in the list.

A more subtle issue is that a bounded type parameter does not make the generic type reifiable. Even with a bound, you cannot use T.class or new T(). The bound only restricts the set of allowed type arguments; it does not give you runtime access to the type. If you need to create instances or inspect types, you must pass a Class<T> object explicitly.

Finally, remember that a type parameter cannot be used in a static context. A static method or field cannot refer to the class's type parameter, because the type parameter is associated with instances. Static methods can declare their own type parameters, but those are independent of the class-level ones.

Understanding these boundaries helps you write generic code that compiles cleanly and behaves predictably. The bound is a compile-time contract; it gives you access to methods and enforces type safety, but it does not change how the JVM stores or inspects the type at runtime.

java bounded type parameter: Practical Usage and Code Exampl | RYUSLOG DEV