Generic Methods

A generic method declares its own type parameter, independent of any enclosing class, letting a single method definition work safely across many types.

A generic method introduces one or more type parameters scoped to just that method, written just before the return type. Unlike a generic class's type parameter, a method's type parameter doesn't require the enclosing class to be generic at all — even an ordinary, non-generic class can have generic (including static) methods.

What Is a Generic Method

The type parameter list appears between the method's modifiers and its return type: <T> before T identity(T value).

Utils itself is not a generic class — only the identity() method is generic, and its type parameter T is scoped to that single method call.

public class Utils {
    public static <T> T identity(T value) {
        return value;
    }
}

String s = Utils.identity("hello");   // T inferred as String
Integer n = Utils.identity(42);       // T inferred as Integer

Method Type Parameters vs. Class Type Parameters

A method's type parameter is independent of the class's, even if a name collides. It is resolved fresh for every individual call to the method.

public class Container<T> {
    private T item;

    // Method-level type parameter U, unrelated to the class's T
    public <U> void printBoth(T classValue, U methodValue) {
        System.out.println(classValue + " and " + methodValue);
    }
}

Container<String> c = new Container<>();
c.printBoth("class-T-value", 123); // U inferred as Integer for this call

Type Inference

In most calls, you don't specify the type argument explicitly — the compiler infers it from the arguments you pass, or from the target context (e.g. the variable you're assigning the result to).

You can specify the type argument explicitly with Utils.<String>identity("x"), but this is rarely necessary — it's mostly used when the compiler can't infer the type unambiguously, such as calling a generic method with no arguments.

public static <T> List<T> singletonList(T item) {
    return List.of(item);
}

List<String> names = singletonList("Ada"); // T inferred as String from the argument
List<Integer> nums = singletonList(1);      // T inferred as Integer

Generic Static Methods

Static generic methods are extremely common as utility/factory methods, because a static context has no instance-level type parameter to fall back on — the method must declare its own if it needs one.

Pair.of(...) is a common "static factory" pattern: it lets callers avoid repeating the type arguments that new Pair<String, Integer>(...) would require, since they're inferred from the call arguments.

public class Pair<A, B> {
    private final A first;
    private final B second;

    private Pair(A first, B second) {
        this.first = first;
        this.second = second;
    }

    // static factory method with its own type parameters A, B
    public static <A, B> Pair<A, B> of(A first, B second) {
        return new Pair<>(first, second);
    }
}

Pair<String, Integer> p = Pair.of("age", 30); // types inferred from arguments

Bounded Type Parameters on Methods (Preview)

A method's type parameter can be bounded with extends, restricting it to types that are (or extend/implement) a given type, which then lets the method call members of that bound on values of the type parameter.

Bounding T to Comparable<T> is what allows a.compareTo(b) to compile — without the bound, T would be treated as plain Object, which has no compareTo(). Bounded type parameters get a dedicated topic.

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

int bigger = Utils.max(3, 7);          // works: Integer implements Comparable<Integer>
String later = Utils.max("apple", "banana"); // works: String implements Comparable<String>

Multiple Type Parameters on a Method

A method can declare more than one type parameter, useful for methods that relate two different types, such as converting or combining values.

import java.util.function.Function;

public static <T, R> R transform(T input, Function<T, R> mapper) {
    return mapper.apply(input);
}

Integer length = transform("hello", String::length); // T=String, R=Integer

Generic Methods That Return the Type Parameter Only

A common, safe pattern for a generic method that has no parameter of type T is that T must be inferred purely from the assignment target, which the compiler can do but only in limited contexts.

import java.util.Collections;

List<String> empty = Collections.<String>emptyList(); // explicit type argument
List<String> empty2 = Collections.emptyList();          // inferred from the declared variable type

Practical Usage

Generic methods are pervasive in utility classes: Collections.sort, Collections.max, Arrays.asList, Objects.requireNonNull, and stream operations like Stream.of and Collectors.toList are all generic methods.

import java.util.Arrays;
import java.util.List;

List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5);
List<Integer> copy = new java.util.ArrayList<>(numbers);
java.util.Collections.sort(copy);          // generic static method
System.out.println(copy);                  // [1, 1, 3, 4, 5]

Edge Cases

Overload resolution with generic methods can be surprising when raw types or autoboxing are involved, and inference can fail (requiring an explicit type witness) when the compiler has no argument to infer from and the target context is ambiguous or absent (e.g. passing the call's result straight into a varargs or Object-typed slot).

public static <T> List<T> listOf() {
    return new ArrayList<>();
}

// List<String> l = listOf();          // OK: inferred from the declared variable type
// printAny(listOf());                 // may fail to infer without more context
printAny(Utils.<String>listOf());       // explicit type witness resolves it

Mental Model

Think of a generic method's <T> as introducing a brand-new, temporary type variable that exists only for the duration of that one call — filled in fresh each time based on the arguments (or, more rarely, the surrounding context), then discarded. This is why the same generic method can be called with a String one moment and an Integer the next: each call gets its own independent binding for T.

Quick Reference

static <T> T identity(T value) { return value; }        // one type parameter
static <T, R> R map(T in, Function<T, R> f) { ... }      // two type parameters
static <T extends Comparable<T>> T max(T a, T b) { ... } // bounded type parameter

Call sites:
  Utils.identity("x")            // T inferred as String
  Utils.<String>identity("x")    // T given explicitly (rarely needed)

Key Takeaways

A generic method declares its own type parameter list (e.g. <T>) before its return type, independent of any enclosing class's type parameters.

A generic method's type parameters are almost always inferred by the compiler from the arguments or the assignment target, so explicit type arguments are rarely written.

Static methods commonly need to be generic because a static context cannot use the enclosing class's own type parameter.

A method's type parameter can be bounded (<T extends Comparable<T>>) to allow calling methods declared on the bound.

When inference cannot determine T from context, an explicit type witness (Utils.<String>method()) resolves the ambiguity.

public class Container<T> { // trying to make a static method use the class's T public static T create(T value) { return value; } // does not compile } public class Container<T> { public static <U> U create(U value) { return value; } // method declares its own type param }

A static method cannot use the enclosing class's type parameter (there is no single T shared by the class itself); it must declare and use its own independent type parameter.

// Forgetting the <T> declaration, so T is treated as an undeclared type public T identity(T value) { return value; } // does not compile (unless T is a class type param) public <T> T identity(T value) { return value; } // <T> declares the method's type parameter

A type parameter must be declared with <T> before it can be used in a method signature; omitting it makes the compiler treat T as an unresolved type name.

List<String> l = someGenericFactoryCall(); // fails to compile when inference has no clues

List<String> l = Utils.<String>someGenericFactoryCall(); // explicit type witness

When a generic method has no argument for the compiler to infer T from, and the target context isn't enough on its own, supply the type argument explicitly with a type witness.

Where does a generic method's type parameter list appear in its declaration?

Immediately before the return type, e.g. public static <T> T identity(T value) — the <T> there declares the method's own type parameter.

How does a generic method's type parameter relate to its enclosing class's type parameter, if any?

They are completely independent, even non-generic classes can have generic methods, and a generic class's method can introduce its own type parameter unrelated to (or even reusing the same letter as, though shadowed) the class's parameter.

Why must many utility/factory methods be declared as generic static methods rather than instance methods on a generic class's type parameter?

A static context has no per-instance type parameter to draw on (there's no single T for the class as a whole), so if a static method needs a type parameter, it must declare and use its own.

How does the compiler usually determine a generic method's type argument at a call site?

It infers it from the types of the arguments passed to the method, or occasionally from the target context such as the declared type of the variable the result is assigned to.

When would you need to write an explicit type witness like Utils.<String>identity(...)?

When the compiler cannot infer the type argument from the arguments or context alone — commonly when the generic method takes no argument that mentions T and the surrounding expression doesn't give enough type information either.

Interview: Why does <T extends Comparable<T>> T max(T a, T b) compile a.compareTo(b) but a plain <T> T max(T a, T b) would not?

Without a bound, T is treated as Object, which has no compareTo() method, so the call wouldn't compile. Bounding T to Comparable<T> tells the compiler every value passed for T has a compareTo() method, allowing the method body to call it safely.

Interview: What is the practical benefit of writing Pair.of(a, b) as a static generic factory method instead of calling new Pair<TypeA, TypeB>(a, b) directly?

The static factory method's type parameters are inferred from the arguments, so the caller never has to spell out the type arguments explicitly, reducing verbosity while remaining fully type-safe.

Write a generic static method <T> void printAll(List<T> items) that prints each item on its own line.

public static <T> void printAll(List<T> items) { for (T item : items) { System.out.println(item); } }

The type parameter T lets printAll accept a List of any element type; since the method only prints elements (relying on Object's toString()), it works uniformly regardless of what T is bound to.

What does this print, and how is T resolved differently for each call?

Prints x1. In the first call, T is inferred as String from both arguments; in the second, T is inferred as Integer. Each call gets its own independent binding for T based on the arguments passed.

Write a generic static method <T extends Comparable<T>> T min(List<T> items) that returns the smallest item in a non-empty list.

public static <T extends Comparable<T>> T min(List<T> items) { T smallest = items.get(0); for (T item : items) { if (item.compareTo(smallest) < 0) { smallest = item; } } return smallest; }

Bounding T to Comparable<T> allows calling item.compareTo(smallest) inside the method body; the method works for any list of a type that implements Comparable against itself, such as Integer, String, or a custom class.

What does this print, and what are T and R bound to in this call?

Prints [1, 2, 3]. T is inferred as String (the element type of the input list) and R is inferred as Integer (the return type of String::length), giving a fully type-checked transformation from List<String> to List<Integer>.

public static <T> T pickFirst(T a, T b) {
    return a;
}

String s = pickFirst("x", "y");
Integer i = pickFirst(1, 2);
System.out.println(s + i);
public static <T, R> List<R> mapAll(List<T> items, java.util.function.Function<T, R> f) {
    List<R> result = new ArrayList<>();
    for (T item : items) {
        result.add(f.apply(item));
    }
    return result;
}

List<Integer> lengths = mapAll(List.of("a", "bb", "ccc"), String::length);
System.out.println(lengths);