Java Generic Methods
Table of Contents + −
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 onThe 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 beforevoiddeclares the type parameter for this method.voidis the return type, as usual. The<T>sits in front of it.T[] arraymeans “an array of any type”.- Inside the loop,
itemis aT, 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
AlexRiya102030You 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
Alex10🧮 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:
KandVare 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
KtoStringandVtoInteger. The second call flips them. - Use multiple type parameters when a method must keep two unrelated types straight at once.
Output
age = 301 = 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 implementsComparable<T>”.- That promise lets the compiler allow
a.compareTo(b)inside the method. compareToreturns a number: negative ifais smaller, zero if equal, positive if larger.- So
a.compareTo(b) >= 0is true whenais greater than or equal tob, and we return the bigger one. - In generics you write
extendseven for an interface likeComparable, neverimplements.
Output
25boxInteger 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
<?>letsprintSizeaccept 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, son.doubleValue()is always safe. - That is why the same
sumaccepts bothList<Integer>andList<Double>. - Without the wildcard,
sum(List<Number>)would reject aList<Integer>. The bound is what makes it flexible.
Output
6.04.0The 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 oursum). - 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 aT. 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, orsum. - 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 isstatic T getFirst(T[] array) { return array[0];}
// ✅ Correct: declare <T> before the return typestatic <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 Numberstatic 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 writestatic 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
? extendsfor parameters you read from, and? superfor parameters you write into. - Give type parameters meaningful single letters.
Tfor a general type,KandVfor key and value,Efor 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, likestatic <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? extendsfor reading and? superfor writing, the PECS idea.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
Where does the type parameter go in a generic method?
Why: A generic method declares its type parameter before the return type.
- 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
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
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.