Generic Methods in Java

Learn how to create methods that work with any data type

Think of generic methods like a universal remote control that works with ANY brand of TV! You don't need a different remote for Samsung, LG, or Sony - one remote works with all of them. Generic methods let you write ONE method that works with any data type instead of writing separate methods for String, Integer, Double, and every other type!

The Problem Without Generic Methods

Without generic methods, you have to write the same method multiple times for different types:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class OldWayPrinter {
// Print String
public void printString(String value) {
System.out.println("String: " + value);
}
// Print Integer
public void printInteger(Integer value) {
System.out.println("Integer: " + value);
}
// Print Double
public void printDouble(Double value) {
System.out.println("Double: " + value);
}
// Print Boolean
public void printBoolean(Boolean value) {
System.out.println("Boolean: " + value);
}
// Print Person
public void printPerson(Person value) {
System.out.println("Person: " + value);
}
// Problem: So much code duplication!
// Each method does the same thing!
}
public class WithoutGenericMethods {
public static void main(String[] args) {
OldWayPrinter printer = new OldWayPrinter();
printer.printString("Hello"); // Method 1
printer.printInteger(42); // Method 2
printer.printDouble(3.14); // Method 3
printer.printBoolean(true); // Method 4
printer.printPerson(new Person("Alice")); // Method 5
// We need a method for EVERY type! 😫
// This is not scalable!
// Solution: Generic Methods! 👇
}
}
class Person {
String name;
Person(String name) { this.name = name; }
public String toString() { return "Person{" + name + "}"; }
}

What is a Generic Method?

A generic method is a method that can work with any type. You specify the type in angle brackets <T> before the return type. The method then works with that type parameter!

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Generic method - works with ANY type!
// Syntax: <T> comes BEFORE the return type
public class GenericPrinter {
// Generic print method
public <T> void print(T value) {
System.out.println("Value: " + value);
}
// Generic swap method
public <T> void swap(T[] arr, int i, int j) {
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// Generic get max method
public <T extends Comparable<T>> T getMax(T[] arr) {
if (arr.length == 0) return null;
T max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i].compareTo(max) > 0) {
max = arr[i];
}
}
return max;
}
}
public class GenericMethodIntro {
public static void main(String[] args) {
GenericPrinter printer = new GenericPrinter();
// Same method, different types!
printer.print("Hello"); // Works with String
printer.print(42); // Works with Integer
printer.print(3.14); // Works with Double
printer.print(true); // Works with Boolean
printer.print(new Person("Alice")); // Works with Person
// One method to rule them all!
// No duplicated code!
// Type safe!
// Type inference in action
// printer.print("Hello") - Java infers T is String
// printer.print(42) - Java infers T is Integer
}
}
class Person {
String name;
Person(String name) { this.name = name; }
public String toString() { return "Person{" + name + "}"; }
}

Static Generic Methods

Static methods can also be generic. This is very useful for utility methods:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import java.util.*;
public class GenericUtilities {
// Static generic method for printing
public static <T> void printArray(T[] arr) {
System.out.print("[");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]);
if (i < arr.length - 1) System.out.print(", ");
}
System.out.println("]");
}
// Static generic swap method
public static <T> void swap(T[] arr, int i, int j) {
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// Static generic method for finding minimum
public static <T extends Comparable<T>> T findMin(T[] arr) {
if (arr.length == 0) return null;
T min = arr[0];
for (T element : arr) {
if (element.compareTo(min) < 0) {
min = element;
}
}
return min;
}
// Static generic method for finding maximum
public static <T extends Comparable<T>> T findMax(T[] arr) {
if (arr.length == 0) return null;
T max = arr[0];
for (T element : arr) {
if (element.compareTo(max) > 0) {
max = element;
}
}
return max;
}
// Static generic method for converting to list
public static <T> List<T> arrayToList(T[] arr) {
return Arrays.asList(arr);
}
// Static generic method for counting occurrences
public static <T> int count(T[] arr, T value) {
int count = 0;
for (T element : arr) {
if (element.equals(value)) {
count++;
}
}
return count;
}
}
public class StaticGenericMethods {
public static void main(String[] args) {
// String array
String[] words = {"apple", "banana", "cherry"};
GenericUtilities.printArray(words); // [apple, banana, cherry]
// Integer array
Integer[] numbers = {5, 2, 8, 1, 9};
GenericUtilities.printArray(numbers); // [5, 2, 8, 1, 9]
System.out.println("Min: " + GenericUtilities.findMin(numbers)); // 1
System.out.println("Max: " + GenericUtilities.findMax(numbers)); // 9
// Double array
Double[] prices = {19.99, 5.50, 12.75, 8.25};
GenericUtilities.printArray(prices);
// Swap test
Integer[] nums = {1, 2, 3};
GenericUtilities.swap(nums, 0, 2);
GenericUtilities.printArray(nums); // [3, 2, 1]
// Convert to list
List<String> wordList = GenericUtilities.arrayToList(words);
System.out.println("List: " + wordList);
// Count occurrences
Integer[] repeating = {1, 2, 2, 3, 2, 4};
int count = GenericUtilities.count(repeating, 2);
System.out.println("Count of 2: " + count); // 3
}
}

Instance Generic Methods

Even in non-generic classes, you can have generic methods:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// Non-generic class with generic methods
public class DataHandler {
private String name;
public DataHandler(String name) {
this.name = name;
}
// Instance generic method
public <T> void process(T data) {
System.out.println(name + " processing: " + data);
}
// Instance generic method that returns type
public <T> T getFirstElement(T[] arr) {
if (arr.length == 0) return null;
return arr[0];
}
// Instance generic method with multiple operations
public <T> void printWithType(T value) {
System.out.println("Type: " + value.getClass().getSimpleName());
System.out.println("Value: " + value);
System.out.println("Hash: " + value.hashCode());
}
// Instance generic method with local type variable
public <T extends Number> double convert(T number) {
return number.doubleValue();
}
}
// Another example: Repository pattern without generics
public class SimpleRepository {
// Generic find method that works with any type
public <T> T findById(List<T> items, String id) {
for (T item : items) {
if (item.toString().contains(id)) {
return item;
}
}
return null;
}
// Generic filter method
public <T> List<T> filterByType(List<Object> items, Class<T> type) {
List<T> result = new ArrayList<>();
for (Object item : items) {
if (type.isInstance(item)) {
result.add(type.cast(item));
}
}
return result;
}
// Generic clone method (simplified)
public <T> List<T> cloneList(List<T> original) {
return new ArrayList<>(original);
}
}
public class InstanceGenericMethods {
public static void main(String[] args) {
// Using DataHandler
DataHandler handler = new DataHandler("Handler1");
handler.process("Hello"); // Works with String
handler.process(42); // Works with Integer
handler.process(3.14); // Works with Double
// Using generic method that returns
String[] fruits = {"apple", "banana", "cherry"};
String first = handler.getFirstElement(fruits);
System.out.println("First fruit: " + first);
Integer[] numbers = {10, 20, 30};
Integer firstNum = handler.getFirstElement(numbers);
System.out.println("First number: " + firstNum);
// Using print with type
handler.printWithType("Java");
handler.printWithType(123);
// Using SimpleRepository
SimpleRepository repo = new SimpleRepository();
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
String found = repo.findById(names, "Bob");
System.out.println("Found: " + found);
// Filter method
List<Object> mixed = new ArrayList<>();
mixed.add("String");
mixed.add(123);
mixed.add(45.67);
mixed.add("Another");
List<String> strings = repo.filterByType(mixed, String.class);
System.out.println("Strings: " + strings);
// Clone method
List<Integer> original = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> cloned = repo.cloneList(original);
System.out.println("Original: " + original);
System.out.println("Cloned: " + cloned);
}
}
class User {
String name;
User(String name) { this.name = name; }
public String toString() { return "User{" + name + "}"; }
}

Type Inference

Java can often figure out what type you mean without you having to specify it:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class TypeInferenceDemo {
// Generic method
public static <T> T identity(T value) {
return value;
}
// Generic method with two parameters
public static <T> void swap(T[] arr, int i, int j) {
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// Generic method that returns first element
public static <T> T getFirst(T... elements) {
return elements.length > 0 ? elements[0] : null;
}
// Generic method with multiple types
public static <K, V> void printPair(K key, V value) {
System.out.println("Key: " + key + ", Value: " + value);
}
}
public class TypeInferenceExamples {
public static void main(String[] args) {
// Type inference in action!
// Explicit type specification (not needed, but allowed)
String str1 = TypeInferenceDemo.<String>identity("Hello");
Integer num1 = TypeInferenceDemo.<Integer>identity(42);
// Type inference - Java figures it out!
String str2 = TypeInferenceDemo.identity("Hello"); // T inferred as String
Integer num2 = TypeInferenceDemo.identity(42); // T inferred as Integer
Double dbl = TypeInferenceDemo.identity(3.14); // T inferred as Double
Boolean bool = TypeInferenceDemo.identity(true); // T inferred as Boolean
// Java infers from the assignment target
String result = TypeInferenceDemo.identity("World"); // T is String
// Java infers from the argument type
Integer[] numbers = {1, 2, 3};
TypeInferenceDemo.swap(numbers, 0, 2); // T inferred as Integer
String[] words = {"apple", "banana"};
TypeInferenceDemo.swap(words, 0, 1); // T inferred as String
// Varargs with type inference
String first = TypeInferenceDemo.getFirst("one", "two", "three");
System.out.println("First: " + first);
Integer firstNum = TypeInferenceDemo.getFirst(10, 20, 30, 40);
System.out.println("First number: " + firstNum);
// Multiple type parameters - inference for both K and V
TypeInferenceDemo.printPair("name", "Alice"); // K=String, V=String
TypeInferenceDemo.printPair(1, "One"); // K=Integer, V=String
TypeInferenceDemo.printPair("age", 25); // K=String, V=Integer
TypeInferenceDemo.printPair("pi", 3.14); // K=String, V=Double
// Type inference is smart!
System.out.println("
--- Type Inference Benefits ---");
System.out.println("Cleaner code - no need to specify types");
System.out.println("Type safe - compiler still checks types");
System.out.println("Flexible - works with any type");
}
}

Multiple Type Parameters

Methods can work with multiple type parameters:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import java.util.*;
public class MultipleTypeParameters {
// Two type parameters
public static <K, V> void printPair(K key, V value) {
System.out.println("Key: " + key + ", Value: " + value);
}
// Three type parameters
public static <A, B, C> void printTriple(A first, B second, C third) {
System.out.println("First: " + first);
System.out.println("Second: " + second);
System.out.println("Third: " + third);
}
// Generic method to convert between types
public static <T, U> U convert(T input, Class<U> targetType) {
if (input instanceof String) {
String str = (String) input;
if (targetType == Integer.class) {
return (U) Integer.valueOf(str);
} else if (targetType == Double.class) {
return (U) Double.valueOf(str);
}
}
return null;
}
// Transform one type to another
public static <T, R> List<R> transformList(List<T> input, Transformer<T, R> transformer) {
List<R> result = new ArrayList<>();
for (T item : input) {
result.add(transformer.transform(item));
}
return result;
}
// Find common type behavior
public static <T, U> void compareTypes(T obj1, U obj2) {
System.out.println("Type 1: " + obj1.getClass().getSimpleName());
System.out.println("Type 2: " + obj2.getClass().getSimpleName());
System.out.println("Same? " + obj1.getClass().equals(obj2.getClass()));
}
// Complex: Map transformation
public static <K, V, N> Map<K, N> transformMapValues(Map<K, V> source,
Transformer<V, N> transformer) {
Map<K, N> result = new HashMap<>();
for (Map.Entry<K, V> entry : source.entrySet()) {
result.put(entry.getKey(), transformer.transform(entry.getValue()));
}
return result;
}
interface Transformer<T, R> {
R transform(T input);
}
}
public class MultipleTypeParametersExample {
public static void main(String[] args) {
// Two type parameters
MultipleTypeParameters.printPair("name", "Alice"); // K=String, V=String
MultipleTypeParameters.printPair(1, "One"); // K=Integer, V=String
MultipleTypeParameters.printPair("count", 42); // K=String, V=Integer
MultipleTypeParameters.printPair("pi", 3.14); // K=String, V=Double
// Three type parameters
System.out.println("
--- Three Parameters ---");
MultipleTypeParameters.printTriple("John", 30, 6.1); // A=String, B=Integer, C=Double
MultipleTypeParameters.printTriple(1, 2.5, true); // A=Integer, B=Double, C=Boolean
// Type conversion
System.out.println("
--- Type Conversion ---");
Integer num = MultipleTypeParameters.convert("123", Integer.class);
System.out.println("Converted: " + num); // 123
Double dbl = MultipleTypeParameters.convert("3.14", Double.class);
System.out.println("Converted: " + dbl); // 3.14
// Transform list
System.out.println("
--- Transform List ---");
List<String> numbers = Arrays.asList("1", "2", "3", "4", "5");
List<Integer> transformed = MultipleTypeParameters.transformList(numbers,
input -> Integer.parseInt(input));
System.out.println("Transformed: " + transformed);
// Transform map values
System.out.println("
--- Transform Map ---");
Map<String, String> nameMap = new HashMap<>();
nameMap.put("user1", "alice");
nameMap.put("user2", "bob");
nameMap.put("user3", "charlie");
Map<String, Integer> lengthMap = MultipleTypeParameters.transformMapValues(
nameMap,
input -> input.length()
);
System.out.println("Name lengths: " + lengthMap);
// Compare types
System.out.println("
--- Compare Types ---");
MultipleTypeParameters.compareTypes("string", 123);
MultipleTypeParameters.compareTypes("hello", "world");
}
}

Real-World Examples

See how generic methods are used in practical applications:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import java.util.*;
import java.util.function.*;
public class RealWorldGenericMethods {
// 1. Generic swap - very common!
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
// 2. Generic search
public static <T> int indexOf(T[] array, T value) {
for (int i = 0; i < array.length; i++) {
if (array[i] != null && array[i].equals(value)) {
return i;
}
}
return -1;
}
// 3. Generic filter
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T item : list) {
if (predicate.test(item)) {
result.add(item);
}
}
return result;
}
// 4. Generic map/transform
public static <T, R> List<R> map(List<T> list, Function<T, R> mapper) {
List<R> result = new ArrayList<>();
for (T item : list) {
result.add(mapper.apply(item));
}
return result;
}
// 5. Generic reduce
public static <T> T reduce(List<T> list, T identity,
BinaryOperator<T> accumulator) {
T result = identity;
for (T item : list) {
result = accumulator.apply(result, item);
}
return result;
}
// 6. Generic cache pattern
public static class Cache<K, V> {
private Map<K, V> data = new HashMap<>();
private Map<K, Long> timestamps = new HashMap<>();
public void put(K key, V value) {
data.put(key, value);
timestamps.put(key, System.currentTimeMillis());
}
public V get(K key) {
return data.get(key);
}
public boolean containsKey(K key) {
return data.containsKey(key);
}
public void clear() {
data.clear();
timestamps.clear();
}
}
// 7. Generic API response wrapper
public static class ApiResponse<T> {
private boolean success;
private T data;
private String error;
public ApiResponse(boolean success, T data, String error) {
this.success = success;
this.data = data;
this.error = error;
}
public static <T> ApiResponse<T> ok(T data) {
return new ApiResponse<>(true, data, null);
}
public static <T> ApiResponse<T> error(String error) {
return new ApiResponse<>(false, null, error);
}
@Override
public String toString() {
return success ? "Success: " + data : "Error: " + error;
}
}
}
public class RealWorldGenericMethodsExample {
public static void main(String[] args) {
// 1. Swap
System.out.println("--- Swap ---");
Integer[] numbers = {1, 2, 3, 4, 5};
System.out.println("Before: " + Arrays.toString(numbers));
RealWorldGenericMethods.swap(numbers, 0, 4);
System.out.println("After: " + Arrays.toString(numbers));
// 2. Search
System.out.println("
--- Search ---");
int index = RealWorldGenericMethods.indexOf(numbers, 3);
System.out.println("Index of 3: " + index);
// 3. Filter
System.out.println("
--- Filter ---");
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evens = RealWorldGenericMethods.filter(list, n -> n % 2 == 0);
System.out.println("Even numbers: " + evens);
// 4. Map/Transform
System.out.println("
--- Transform ---");
List<String> words = Arrays.asList("hello", "world", "java");
List<Integer> lengths = RealWorldGenericMethods.map(words, String::length);
System.out.println("Lengths: " + lengths);
List<String> upper = RealWorldGenericMethods.map(words, String::toUpperCase);
System.out.println("Uppercase: " + upper);
// 5. Reduce
System.out.println("
--- Reduce ---");
Integer sum = RealWorldGenericMethods.reduce(list, 0, (a, b) -> a + b);
System.out.println("Sum: " + sum);
// 6. Cache
System.out.println("
--- Cache ---");
RealWorldGenericMethods.Cache<String, String> cache =
new RealWorldGenericMethods.Cache<>();
cache.put("key1", "value1");
cache.put("key2", "value2");
System.out.println("Cached value: " + cache.get("key1"));
// 7. API Response
System.out.println("
--- API Response ---");
RealWorldGenericMethods.ApiResponse<String> response =
RealWorldGenericMethods.ApiResponse.ok("Data loaded");
System.out.println(response);
RealWorldGenericMethods.ApiResponse<Integer> errorResponse =
RealWorldGenericMethods.ApiResponse.error("Not found");
System.out.println(errorResponse);
}
}

Key Concepts

Type Safety

Generic methods catch type errors at compile time, ensuring type safety without needing to cast!

Code Reusability

Write one generic method instead of multiple overloaded methods for different types!

Type Parameters

Type parameters in methods work like variables that represent actual types at runtime!

Syntax

Place <T> between visibility modifier and return type: public <T> T genericMethod(T param)

Best Practices

  • Use meaningful type parameter names (T for Type, E for Element, K for Key, V for Value)
  • Generic methods are best for utility methods that should work with any type
  • Type parameters are inferred automatically - let Java figure out the types
  • Use wildcards (?) for more flexible method signatures when appropriate
  • Combine generic methods with bounded types when you need type constraints
  • Document what types are expected and any method constraints

Common Mistakes

Forgetting to declare type parameter before return type

Why it's wrong: Generic method syntax requires <T> between visibility modifier and return type: public <T> T method(T param). Forgetting this makes the method non-generic!

Mixing generic methods with generic classes incorrectly

Why it's wrong: A generic method in a non-generic class has its own type parameters. Class<T> and method <E> are different!

Using raw types when calling generic methods

Why it's wrong: Always provide type information when calling generic methods for type safety.

Not understanding type inference

Why it's wrong: Java infers types from the method arguments, so method<String>("hello") is usually unnecessary - just call method("hello")!

Interview Tips

  • 💡Explain that generic methods are different from generic classes - method type parameters are local to that method
  • 💡Know how to declare a generic method with <T> before the return type
  • 💡Understand type inference - Java figures out types from arguments
  • 💡Be able to create methods with multiple type parameters
  • 💡Know the difference between static generic methods and instance generic methods
  • 💡Explain real-world use cases: swap methods, search methods, transform methods