Java Generic Methods

In the last lesson you learned about Java generic classes. But sometimes just one method should work with any type, while the class around it stays normal, like a method that prints any array. For that, Java lets you make a single method generic. Let’s learn generic methods.

🤔 Why a generic method?

Imagine a utility method that prints every element of an array. Write it for one type and you need a copy for every other type.

static void printStrings(String[] arr) { ... }
static void printIntegers(Integer[] arr) { ... }
static void printDoubles(Double[] arr) { ... } // and on and on

The bodies are identical. Only the parameter type changes. A generic method fixes that:

  • You do not want a whole generic class. You want one method that works for any array type.
  • A generic method gets its own type parameter, separate from any class.
  • One method body then serves every type. Full type safety. No casting.

🧩 The generic method syntax

A generic method declares its own type parameter in angle brackets, placed before the return type. Here is the smallest one, which prints any array.

static <T> void printArray(T[] array) {
for (T item : array) {
System.out.println(item);
}
}

Reading it left to right:

  • <T> right before void declares the type parameter for this method.
  • void is the return type, as usual. The <T> sits in front of it.
  • T[] array means “an array of any type”.
  • Inside the loop, item is a T, so each element is that same type.
  • The <T> belongs to the method, not the class. The class can stay normal.

The type parameter goes before the return type

For a generic method, the <T> comes before the return type, like static <T> void printArray(...). This is different from a generic class, where <T> goes after the class name. Forgetting the <T> before the return type is a common error.

💡 Using a generic method

You call a generic method like any normal method. Java figures out the type from the argument you pass. Here we call printArray with two different array types.

public class Main {
static <T> void printArray(T[] array) {
for (T item : array) {
System.out.println(item);
}
}
public static void main(String[] args) {
String[] names = {"Alex", "Riya"};
Integer[] numbers = {10, 20, 30};
printArray(names); // T becomes String
printArray(numbers); // T becomes Integer
}
}

Notice what we did not do:

  • No type in the call. No Main.<String>printArray(...) needed.
  • No casting.
  • No second method.

Java works out the type each time from the argument. This is called type inference. It sees String[], so T is String. It sees Integer[], so T is Integer. One method body handled both.

Output

Alex
Riya
10
20
30

You can pass the type explicitly if you ever need to, like Main.<String>printArray(names). But you almost never do. Inference handles it for you.

🔁 A generic method that returns a type

A generic method can also return the type parameter, so the output type adapts to the input. Here is one that returns the first element of any array.

public class Main {
static <T> T getFirst(T[] array) {
return array[0]; // returns whatever type the array holds
}
public static void main(String[] args) {
String[] names = {"Alex", "Riya"};
Integer[] numbers = {10, 20};
String firstName = getFirst(names); // returns a String
int firstNumber = getFirst(numbers); // returns an Integer
System.out.println(firstName);
System.out.println(firstNumber);
}
}

The signature static <T> T getFirst(T[] array) has two T markers doing different jobs:

  • The first <T> (before the return type) declares the type parameter.
  • The second T (the return type) says the method gives back a value of that same type.
  • So a String array returns a String, an Integer array returns an Integer. No casting.

Output

Alex
10

🧮 Multiple type parameters

A method can declare several type parameters, separated by commas. Here is one that takes two values of different types and prints them as a pair.

public class Main {
static <K, V> void printPair(K key, V value) {
System.out.println(key + " = " + value);
}
public static void main(String[] args) {
printPair("age", 30); // K is String, V is Integer
printPair(1, "first place"); // K is Integer, V is String
}
}

The declaration <K, V> introduces two independent type parameters:

  • K and V are just a convention for “key” and “value”. You could name them anything.
  • Each one is inferred separately from its matching argument.
  • The first call sets K to String and V to Integer. The second call flips them.
  • Use multiple type parameters when a method must keep two unrelated types straight at once.

Output

age = 30
1 = first place

🔒 Bounded type parameters

So far T could be anything. But some methods only make sense for types that can do a certain thing. A max method must compare its values, and a plain T cannot promise compareTo. A bounded type parameter solves this. You add extends to say “T must implement Comparable”, which unlocks the comparison. Here is a generic max method.

public class Main {
static <T extends Comparable<T>> T max(T a, T b) {
// a.compareTo(b) is allowed because T is Comparable
return (a.compareTo(b) >= 0) ? a : b;
}
public static void main(String[] args) {
System.out.println(max(10, 25)); // Integers
System.out.println(max("apple", "box")); // Strings, compared alphabetically
}
}

The signature <T extends Comparable<T>> breaks down like this:

  • T extends Comparable<T> means “T can be any type, as long as it implements Comparable<T>”.
  • That promise lets the compiler allow a.compareTo(b) inside the method.
  • compareTo returns a number: negative if a is smaller, zero if equal, positive if larger.
  • So a.compareTo(b) >= 0 is true when a is greater than or equal to b, and we return the bigger one.
  • In generics you write extends even for an interface like Comparable, never implements.

Output

25
box

Integer and String both implement Comparable, so both calls work. A non-comparable type would be stopped at compile time.

❓ Wildcards: the ? symbol

You will sometimes see a ? in generic code, like List<?>. This is a wildcard, and it means “some unknown type”. Use it when a method accepts a collection of any type but never names that type. There are three forms. First, the import these examples use.

import java.util.List;

The unbounded wildcard: List<?>

The plain ? means “a list of some unknown type”. Use it when the method does things that do not depend on the element type, like reading the size.

static void printSize(List<?> list) {
System.out.println("Size: " + list.size()); // works for a list of anything
}
  • The <?> lets printSize accept any list: List<String>, List<Integer>, all of them.
  • It only reads size(), which is the same for every element type.
  • You cannot add normal elements. The type is unknown, so the compiler blocks additions to stay safe.

The upper bound: List<? extends Number> for reading

An upper-bounded wildcard says “a list of Number or a subtype of Number”. Use it when you want to read numbers out of a list and do maths on them. Here is a method that sums any list of numbers.

public class Main {
static double sum(List<? extends Number> numbers) {
double total = 0;
for (Number n : numbers) { // each item is safely a Number
total += n.doubleValue(); // read it as a double
}
return total;
}
public static void main(String[] args) {
List<Integer> ints = List.of(1, 2, 3);
List<Double> doubles = List.of(1.5, 2.5);
System.out.println(sum(ints)); // accepts List<Integer>
System.out.println(sum(doubles)); // accepts List<Double>
}
}

Reading the signature List<? extends Number>:

  • It means “a list of Number or anything below it”, so Integer, Double, Long, and more.
  • Every element is at least a Number, so n.doubleValue() is always safe.
  • That is why the same sum accepts both List<Integer> and List<Double>.
  • Without the wildcard, sum(List<Number>) would reject a List<Integer>. The bound is what makes it flexible.

Output

6.0
4.0

The lower bound and PECS (one quick note)

There is also a lower-bounded wildcard, List<? super Integer>, meaning “a list of Integer or any supertype of Integer”. Use it to write Integer values into a list. A short memory aid picks between the two:

  • PECS = Producer Extends, Consumer Super.
  • If the collection produces values you read, use ? extends (like our sum).
  • If the collection consumes values you add, use ? super.
  • If you only care about the structure, use the unbounded ?.

🆚 Generic method vs generic class

Which do you pick? It comes down to what needs to vary by type.

  • Use a generic class when the type belongs to the whole object and many methods share it, like a Box<T> that stores and returns a T. The class remembers the type across calls.
  • Use a generic method when only one operation is generic and nothing is stored between calls, like printArray, max, or sum.
  • Rule of thumb: if the type would be a field of the object, make the class generic. If it lives only for one call, make the method generic.

⚠️ Common Mistakes

A few generic-method slip-ups to watch for.

Forgetting the <T> before the return type. Then the compiler thinks T is a real class it cannot find.

// ❌ Wrong: T is never declared, so the compiler does not know what T is
static T getFirst(T[] array) {
return array[0];
}
// ✅ Correct: declare <T> before the return type
static <T> T getFirst(T[] array) {
return array[0];
}

Trying to add to a List<? extends T>. That wildcard is for reading, not adding. The element type is unknown, so the compiler blocks additions.

// ❌ Wrong: cannot add to a list of an unknown subtype of Number
static void addOne(List<? extends Number> list) {
list.add(1); // compile error: the real type is unknown
}
// ✅ Correct: read from ? extends, and use a concrete or ? super list to write
static void addOne(List<? super Integer> list) {
list.add(1); // fine: the list accepts Integer
}

Confusing a generic method with a generic class. A generic method has its own type parameter and lives fine in a normal class. The class need not be generic.

Writing the type at the call unnecessarily. Java infers it from the arguments, so printArray(names) is enough. Main.<String>printArray(names) just adds noise.

✅ Best Practices

Habits for writing clean generic methods.

  • Reach for a generic method when only a method needs to be generic. No need to make the whole class generic.
  • Put <T> before the return type. That is the required and only spot for a method’s type parameter.
  • Let Java infer the type. Call the method normally and let the compiler work out the type from the arguments.
  • Add a bound when you need an ability. Use <T extends Comparable<T>> when the method must compare, sort, or otherwise rely on what the type can do.
  • Follow PECS for wildcards. Use ? extends for parameters you read from, and ? super for parameters you write into.
  • Give type parameters meaningful single letters. T for a general type, K and V for key and value, E for element. It is a convention every Java reader knows.

🧩 What You’ve Learned

Great, that completes the Generics module. Let’s recap generic methods.

  • ✅ A generic method has its own type parameter, so a single method works with any type.
  • ✅ The type parameter <T> goes before the return type, like static <T> void printArray(...).
  • ✅ Java infers the type from the arguments, so you call it like any normal method.
  • ✅ A generic method can return the type parameter, adapting its return type to the input.
  • ✅ You can declare multiple type parameters like <K, V>, and add a bound like <T extends Comparable<T>> to unlock abilities.
  • ✅ A wildcard ? means “some unknown type”; use ? extends for reading and ? super for writing, the PECS idea.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    Where does the type parameter go in a generic method?

    Why: A generic method declares its type parameter before the return type.

  2. 2

    Does the class need to be generic for a method to be generic?

    Why: A generic method has its own type parameter and works fine inside a non-generic class.

  3. 3

    How does Java usually know what type T is when you call a generic method?

    Why: Java infers the type parameter from the arguments, so you rarely write it explicitly.

  4. 4

    What does the wildcard ? mean in List<?>?

    Why: The wildcard ? stands for some unknown type, useful for flexible read-only parameters.

🚀 What’s Next?

You have a solid grip on generic methods now. Next we go deeper into the wildcard syntax you just met, the ? symbol, and learn exactly when to reach for upper and lower bounds. Let’s continue with wildcards.

Java Generics Wildcards

Share & Connect