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
Alex10Multiple 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 placeBounded 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.