Method References in Java

Learn the ultimate shortcut for writing super clean lambda code

Method references are like nicknames for methods! Instead of writing out a full lambda expression like (a, b) -> a.compareTo(b), you just say String::compareTo and Java understands exactly what you mean. It's lambda expressions taken to the next level - shorter, cleaner, and more readable!

What is a Method Reference?

A method reference is a shorthand notation for a lambda expression that just calls an existing method. Instead of writing a lambda that does nothing but call another method, you use a method reference syntax (::) to reference it directly!

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
// Lambda way: Write out the whole thing
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// Old way with lambda:
names.forEach(name -> System.out.println(name));
// Method Reference way: Just reference the method!
names.forEach(System.out::println);
// Both do the EXACT same thing, but method reference is shorter!
// Another example:
// Lambda version:
names.sort((a, b) -> a.compareTo(b));
// Method Reference version:
names.sort(String::compareTo);
// Lambda version:
numbers.stream()
.map(n -> n.toString())
.collect(Collectors.toList());
// Method Reference version:
numbers.stream()
.map(Object::toString)
.collect(Collectors.toList());
// Why use method references?
// 1. Shorter and cleaner code
// 2. More readable (the method name is explicit)
// 3. Less boilerplate
// 4. Your intention is crystal clear

Four Types of Method References

There are four different kinds of method references you can use:

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
// TYPE 1: Static Method Reference
// Syntax: ClassName::staticMethod
// Used when: You want to call a static method
// Lambda equivalent: (params) -> ClassName.staticMethod(params)
// Example:
Comparator<Integer> comp = Integer::compare;
// Instead of: (a, b) -> Integer.compare(a, b)
Function<String, Integer> parser = Integer::parseInt;
// Instead of: (str) -> Integer.parseInt(str)
// TYPE 2: Instance Method Reference (specific object)
// Syntax: objectReference::instanceMethod
// Used when: You have an object and want to call its method
// Lambda equivalent: (params) -> objectReference.method(params)
// Example:
String str = "HELLO";
Supplier<Integer> supplier = str::length;
// Instead of: () -> str.length()
Consumer<String> printer = System.out::println;
// Instead of: (msg) -> System.out.println(msg)
// TYPE 3: Constructor Reference
// Syntax: ClassName::new
// Used when: You want to create new instances
// Lambda equivalent: (params) -> new ClassName(params)
// Example:
Supplier<ArrayList> supplier = ArrayList::new;
// Instead of: () -> new ArrayList()
Function<String, List> listMaker = ArrayList::new;
// Instead of: (initial) -> new ArrayList(initial)
// TYPE 4: Instance Method Reference (arbitrary object)
// Syntax: ClassName::instanceMethod
// Used when: Method called on the FIRST parameter
// Lambda equivalent: (obj, params) -> obj.method(params)
// Example:
BiFunction<String, String, Integer> comparator = String::compareTo;
// Instead of: (a, b) -> a.compareTo(b)
// The FIRST parameter (a) is the object, (b) is the argument
Function<String, Integer> lengthGetter = String::length;
// Instead of: (str) -> str.length()
// The parameter is the object
// Summary table:
// TYPE | SYNTAX | WHEN TO USE
// Static method | Class::staticMethod | Calling static methods
// Instance (specific) | obj::method | Calling method on known object
// Constructor | Class::new | Creating new objects
// Instance (arbitrary) | Class::method | First param is the object

Static Method References

Reference methods that belong to a class, not an instance:

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
import java.util.*;
import java.util.stream.*;
public class StaticMethodReferences {
public static void main(String[] args) {
// Example 1: Math methods
List<Integer> numbers = Arrays.asList(-5, 3, -8, 10, 2);
// Using method reference
numbers.stream()
.map(Math::abs) // Reference to Math.abs(n)
.forEach(System.out::println);
// Output: 5, 3, 8, 10, 2
// Equivalent lambda:
// .map(n -> Math.abs(n))
// Example 2: Parsing numbers
List<String> strings = Arrays.asList("123", "456", "789");
List<Integer> parsed = strings.stream()
.map(Integer::parseInt) // Reference to Integer.parseInt(str)
.collect(Collectors.toList());
System.out.println(parsed); // [123, 456, 789]
// Equivalent lambda:
// .map(str -> Integer.parseInt(str))
// Example 3: Custom static methods
List<String> names = Arrays.asList("alice", "BOB", "charlie");
List<String> validated = names.stream()
.filter(StringValidator::isValidName) // Custom static method
.collect(Collectors.toList());
System.out.println(validated); // [alice, charlie]
// Example 4: Creating objects with static factory
List<String> jsonStrings = Arrays.asList(
"{\"name\":\"Alice\"}",
"{\"name\":\"Bob\"}"
);
List<User> users = jsonStrings.stream()
.map(User::fromJson) // Static factory method
.collect(Collectors.toList());
System.out.println(users);
// Example 5: Using Comparator with static method
List<String> words = Arrays.asList("apple", "pie", "a", "programming");
words.sort(String::compareToIgnoreCase);
System.out.println(words); // [a, apple, pie, programming]
// Example 6: Supplier with static method
Supplier<Long> currentTime = System::currentTimeMillis;
System.out.println("Time: " + currentTime.get());
// All of these are static method references:
// System::currentTimeMillis
// Math::abs
// Integer::parseInt
// Double::parseDouble
// String::valueOf
// Arrays::asList
}
}
class StringValidator {
static boolean isValidName(String name) {
return !name.isEmpty() && name.matches("[a-z]+");
}
}
class User {
String name;
User(String name) {
this.name = name;
}
static User fromJson(String json) {
// Simulate parsing JSON
return new User("User from JSON");
}
public String toString() {
return "User(" + name + ")";
}
}

Instance Method References

Reference methods from an object that already exists:

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
import java.util.*;
public class InstanceMethodReferences {
public static void main(String[] args) {
// Example 1: PrintStream method reference
List<String> messages = Arrays.asList("Hello", "World", "Java");
// Using method reference on System.out object
messages.forEach(System.out::println);
// Output:
// Hello
// World
// Java
// Equivalent lambda:
// messages.forEach(msg -> System.out.println(msg))
// Example 2: String methods on a specific object
String greeting = " Hello World ";
// Using method reference on greeting object
Supplier<String> trimmed = greeting::trim;
System.out.println("Trimmed: '" + trimmed.get() + "'");
Supplier<String> upper = greeting::toUpperCase;
System.out.println("Upper: " + upper.get());
// Equivalent lambdas:
// Supplier<String> trimmed = () -> greeting.trim()
// Supplier<String> upper = () -> greeting.toUpperCase()
// Example 3: Custom object methods
Calculator calc = new Calculator();
Function<Integer, Integer> doubler = calc::multiply;
// Reference to calc.multiply(n) method
System.out.println("Double 5: " + doubler.apply(5)); // 10
// Equivalent lambda:
// Function<Integer, Integer> doubler = (n) -> calc.multiply(n)
// Example 4: Multiple instance method references
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5);
// Shuffle using a specific random instance
Random random = new Random(42);
// You could shuffle like: Collections.shuffle(numbers, (a, b) -> random.nextInt())
// Example 5: Instance methods with parameters
StringProcessor processor = new StringProcessor();
Function<String, Integer> counter = processor::countWords;
System.out.println("Words in 'Hello World Java': " +
counter.apply("Hello World Java")); // 3
// Example 6: Collections operations
List<String> names = new ArrayList<>(
Arrays.asList("Charlie", "Alice", "Bob")
);
Comparator<String> byLength = String::compareTo;
names.sort(byLength);
System.out.println("Sorted: " + names);
// Example 7: PrintStream operations
System.out.println("\nPrinting collection:");
names.forEach(System.out::println);
// All of these are instance method references on specific objects:
// System.out::println
// greeting::trim
// calc::multiply
// processor::countWords
}
}
class Calculator {
int multiply(int n) {
return n * 2;
}
int add(int a, int b) {
return a + b;
}
}
class StringProcessor {
int countWords(String text) {
return text.trim().split("\\s+").length;
}
String reverse(String text) {
return new StringBuilder(text).reverse().toString();
}
}

Constructor References

Create new objects without writing the 'new' keyword:

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
import java.util.*;
import java.util.stream.*;
public class ConstructorReferences {
public static void main(String[] args) {
// Example 1: Creating lists with constructor reference
Supplier<List> listSupplier = ArrayList::new;
// Instead of: () -> new ArrayList()
List<String> newList = listSupplier.get();
newList.add("Hello");
System.out.println(newList); // [Hello]
// Example 2: Creating HashSet
Supplier<Set> setSupplier = HashSet::new;
// Instead of: () -> new HashSet()
Set<String> newSet = setSupplier.get();
newSet.add("World");
System.out.println(newSet); // [World]
// Example 3: Creating custom objects
Supplier<Person> personSupplier = Person::new;
// Instead of: () -> new Person()
Person p1 = personSupplier.get();
System.out.println(p1); // Person(Unknown, 0)
// Example 4: Creating with parameters
Function<String, Person> fromName = Person::new;
// Instead of: (name) -> new Person(name)
Person p2 = fromName.apply("Alice");
System.out.println(p2); // Person(Alice, 0)
// Example 5: Creating with multiple parameters
BiFunction<String, Integer, Person> fromNameAndAge = Person::new;
// Instead of: (name, age) -> new Person(name, age)
Person p3 = fromNameAndAge.apply("Bob", 30);
System.out.println(p3); // Person(Bob, 30)
// Example 6: Using constructor references with streams
List<String> names = Arrays.asList("Charlie", "Diana", "Eve");
List<Person> people = names.stream()
.map(Person::new) // Constructor reference!
.collect(Collectors.toList());
System.out.println("\nPeople created from names:");
people.forEach(System.out::println);
// Example 7: Creating arrays
IntFunction<String[]> arrayMaker = String[]::new;
// Instead of: (size) -> new String[size]
String[] arr = arrayMaker.apply(5);
System.out.println("Array size: " + arr.length); // 5
// Example 8: Real-world stream example
List<String> jsonData = Arrays.asList(
"Frank",
"Grace",
"Henry"
);
// Create Person objects from strings using constructor reference
List<Person> persons = jsonData.stream()
.map(Person::new) // Person::new calls Person(String name)
.peek(p -> p.birthday()) // Add a year
.collect(Collectors.toList());
System.out.println("\nPersons with age:");
persons.forEach(System.out::println);
// Constructor references work because they match functional interfaces:
// Supplier<T> needs: () -> T
// Function<A, T> needs: A -> T
// BiFunction<A, B, T> needs: (A, B) -> T
// IntFunction<T> needs: int -> T
}
}
class Person {
private String name;
private int age;
// No-arg constructor
Person() {
this.name = "Unknown";
this.age = 0;
}
// Single parameter constructor
Person(String name) {
this.name = name;
this.age = 0;
}
// Two parameter constructor
Person(String name, int age) {
this.name = name;
this.age = age;
}
void birthday() {
this.age++;
}
public String toString() {
return "Person(" + name + ", " + age + ")";
}
}

Converting Lambdas to Method References

See how to transform lambda expressions into cleaner method references:

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
import java.util.*;
public class ConvertingLambdaToMethodReference {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// CONVERSION GUIDE
// ===== CONVERSION 1: forEach =====
// Lambda version:
names.forEach(name -> System.out.println(name));
// Method Reference version:
names.forEach(System.out::println);
// ===== CONVERSION 2: map (static method) =====
// Lambda version:
numbers.stream()
.map(n -> String.valueOf(n))
.forEach(System.out::println);
// Method Reference version:
numbers.stream()
.map(String::valueOf)
.forEach(System.out::println);
// ===== CONVERSION 3: map (instance method on first param) =====
// Lambda version:
names.stream()
.map(name -> name.toUpperCase())
.forEach(System.out::println);
// Method Reference version:
names.stream()
.map(String::toUpperCase)
.forEach(System.out::println);
// ===== CONVERSION 4: filter =====
// Lambda version:
names.stream()
.filter(name -> name.length() > 3)
.forEach(System.out::println);
// Method Reference version (with custom method):
names.stream()
.filter(StringUtil::isLongName)
.forEach(System.out::println);
// ===== CONVERSION 5: sort =====
// Lambda version:
names.sort((a, b) -> a.compareTo(b));
// Method Reference version:
names.sort(String::compareTo);
// ===== CONVERSION 6: constructor =====
// Lambda version:
names.stream()
.map(name -> new Person(name))
.forEach(System.out::println);
// Method Reference version:
names.stream()
.map(Person::new)
.forEach(System.out::println);
// ===== CONVERSION 7: custom class instance method =====
Calculator calc = new Calculator();
// Lambda version:
numbers.stream()
.map(n -> calc.square(n))
.forEach(System.out::println);
// Method Reference version:
numbers.stream()
.map(calc::square)
.forEach(System.out::println);
// ===== CONVERSION RULES =====
// 1. Static method: Class::staticMethod
// (x) -> Math.abs(x) → Math::abs
// 2. Instance method (specific object): object::method
// (x) -> calc.square(x) → calc::square
// 3. Instance method (first param): Class::method
// (str) -> str.toUpperCase() → String::toUpperCase
// 4. Constructor: Class::new
// (x) -> new Person(x) → Person::new
System.out.println("\nKey Points:");
System.out.println("- Only convert if the lambda just calls one method");
System.out.println("- Method reference must match the functional interface");
System.out.println("- Method reference is more readable when it's a well-known method");
}
}
class StringUtil {
static boolean isLongName(String name) {
return name.length() > 3;
}
}
class Person {
String name;
Person(String name) {
this.name = name;
}
public String toString() {
return "Person(" + name + ")";
}
}
class Calculator {
int square(int n) {
return n * n;
}
}

Real-World Usage Patterns

See how method references are used in everyday programming:

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
import java.util.*;
import java.util.stream.*;
public class RealWorldMethodReferences {
public static void main(String[] args) {
// Example 1: Sorting with method references
List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
// Sort alphabetically
names.sort(String::compareTo);
System.out.println("Sorted: " + names);
// Sort by length using custom comparator
names.sort(Comparator.comparingInt(String::length));
System.out.println("By length: " + names);
// Example 2: Filtering and transforming
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<String> result = numbers.stream()
.filter(n -> n % 2 == 0) // Keep even
.map(String::valueOf) // Convert to String
.collect(Collectors.toList());
System.out.println("\nEven numbers as strings: " + result);
// Example 3: Null-safe operations
List<String> items = Arrays.asList("Hello", null, "World");
items.stream()
.filter(Objects::nonNull) // Remove nulls
.map(String::toUpperCase) // Convert to uppercase
.forEach(System.out::println);
System.out.println();
// Example 4: Custom class operations
List<User> users = Arrays.asList(
new User("Alice", "alice@email.com"),
new User("Bob", "bob@email.com"),
new User("Charlie", "charlie@email.com")
);
// Sort by name
users.sort(Comparator.comparing(User::getName));
System.out.println("\nUsers sorted by name:");
users.forEach(System.out::println);
// Example 5: Collecting with method references
Map<String, User> userMap = users.stream()
.collect(Collectors.toMap(User::getName, u -> u));
System.out.println("\nUser map: " + userMap);
// Example 6: Double colon with different reference types
System.out.println("\nAll reference types in action:");
// Static method reference
double absolute = Math.abs(-42.5);
System.out.println("Absolute value: " + absolute);
// Instance method reference (specific object)
System.out.println("Printing items:");
items.forEach(System.out::println);
// Instance method reference (first param)
numbers.stream()
.limit(3)
.map(String::valueOf)
.forEach(System.out::println);
// Constructor reference
List<Integer> sizes = Arrays.asList(2, 3, 1);
List<String[]> arrays = sizes.stream()
.map(String[]::new) // Create string arrays
.collect(Collectors.toList());
System.out.println("\nArrays created: " + arrays.size());
// Example 7: Method reference chaining
String text = " hello world ";
String processed = Optional.of(text)
.map(String::trim)
.map(String::toUpperCase)
.orElse("EMPTY");
System.out.println("Processed: " + processed);
// Example 8: Exception handling with method reference
List<String> numberStrings = Arrays.asList("1", "2", "abc", "4");
numbers.stream()
.map(n -> String.valueOf(n))
.map(StringUtil::parseInteger)
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(System.out::println);
}
}
class User {
private String name;
private String email;
User(String name, String email) {
this.name = name;
this.email = email;
}
String getName() {
return name;
}
String getEmail() {
return email;
}
public String toString() {
return "User(" + name + ", " + email + ")";
}
}
class StringUtil {
static Optional<Integer> parseInteger(String str) {
try {
return Optional.of(Integer.parseInt(str));
} catch (NumberFormatException e) {
return Optional.empty();
}
}
}

Key Concepts

Method Reference Syntax (::)

Use Class::method or object::method to reference a method. The :: operator separates the class/object from the method name.

Replaces Simple Lambdas

Method references work best when your lambda just calls an existing method without doing anything else.

Four Types

Static references, instance references, constructor references, and arbitrary object type references.

Type Inference

Java automatically figures out what the method reference does based on the context - no need to specify types!

Best Practices

  • Use method references instead of lambdas when the lambda just calls another method
  • Method references are more readable than lambdas for simple method calls
  • Use constructor references (Class::new) to create functional interfaces
  • Remember: method references only work when the signature matches the functional interface
  • Don't force method references if a lambda is clearer and more readable
  • Combine method references with streams for powerful data processing

Common Mistakes

Using method references when lambda would be clearer

Why it's wrong: Method references should improve readability. If a lambda is clearer, use the lambda!

Method reference with wrong signature

Why it's wrong: The method you reference must have the same parameters and return type as the functional interface.

Using :: with instance methods on the class instead of object

Why it's wrong: For instance methods, reference a specific object: obj::method, not Class::method

Confusing static and instance method references

Why it's wrong: Static: Class::method (no instance needed), Instance: obj::method (specific instance required)

Interview Tips

  • 💡Explain that method references are shorthand for simple lambda expressions
  • 💡Know the four types: static, instance, constructor, and arbitrary object type
  • 💡Be able to convert between lambdas and method references
  • 💡Understand the :: operator and when to use it
  • 💡Explain that method references improve code readability and cleanliness
  • 💡Know common use cases: sorting, filtering, mapping with streams, creating objects