Method References
A method reference is a shorthand for a lambda that does nothing but call an existing method — trading a tiny bit of flexibility for noticeably better readability.
A method reference (::) is compact syntax for a lambda expression whose entire body is a single call to an already-existing method or constructor. Whenever a lambda would just forward its parameters unchanged to another method, a method reference says the same thing with less ceremony, while still being a valid functional-interface implementation.
What Is a Method Reference
Method references use the :: operator to point at a method by name instead of writing out a lambda that calls it. They compile to the same kind of functional-interface implementation as the equivalent lambda — same target typing rules apply.
import java.util.function.Function;
Function<String, Integer> len1 = s -> s.length(); // lambda
Function<String, Integer> len2 = String::length; // equivalent method reference
System.out.println(len1.apply("hello")); // 5
System.out.println(len2.apply("hello")); // 5Static Method Reference
Form: ClassName::staticMethod. Refers to a static method; the functional interface's parameters map directly to the static method's parameters.
import java.util.function.Function;
import java.util.function.BiFunction;
Function<String, Integer> parse = Integer::parseInt; // Integer.parseInt(String)
System.out.println(parse.apply("42")); // 42
BiFunction<Integer, Integer, Integer> max = Math::max; // Math.max(int, int)
System.out.println(max.apply(3, 7)); // 7Bound Instance Method Reference
Form: instance::instanceMethod. Refers to an instance method on a specific, already-existing object — the object ('receiver') is bound at the time the reference is created, exactly like a lambda that captures that object.
import java.util.function.Supplier;
import java.util.function.Predicate;
String greeting = "Hello, World!";
Supplier<Integer> lengthSupplier = greeting::length; // bound to this specific String
System.out.println(lengthSupplier.get()); // 13
List<String> log = new ArrayList<>();
Consumer<String> addToLog = log::add; // bound to this specific List instance
addToLog.accept("event 1");Unbound Instance Method Reference
Form: ClassName::instanceMethod. Refers to an instance method without a specific receiver bound yet — the first parameter of the functional interface becomes the object the method is called ON, and the remaining parameters become the method's own arguments.
import java.util.function.Function;
import java.util.function.BiFunction;
Function<String, Integer> len = String::length; // s -> s.length(), 's' supplies the receiver
System.out.println(len.apply("hello")); // 5
BiFunction<String, String, Boolean> startsWith = String::startsWith; // (s, prefix) -> s.startsWith(prefix)
System.out.println(startsWith.apply("hello world", "hello")); // trueConstructor Reference
Form: ClassName::new. Refers to a constructor, matching whichever overload fits the functional interface's parameter list. Works for regular classes and arrays.
import java.util.function.Supplier;
import java.util.function.Function;
import java.util.function.IntFunction;
Supplier<ArrayList<String>> listMaker = ArrayList::new; // no-arg constructor
ArrayList<String> list = listMaker.get();
Function<String, StringBuilder> sbMaker = StringBuilder::new; // StringBuilder(String) constructor
StringBuilder sb = sbMaker.apply("start");
IntFunction<int[]> arrayMaker = int[]::new; // array constructor reference
int[] arr = arrayMaker.apply(5); // new int[5]The Four Kinds, Side by Side
Kind Syntax Equivalent lambda
Static ClassName::staticM (args) -> ClassName.staticM(args)
Bound instance instance::instM (args) -> instance.instM(args)
Unbound instance ClassName::instM (recv, args) -> recv.instM(args)
Constructor ClassName::new (args) -> new ClassName(args)Readability Benefits
Method references shine when the lambda would add nothing beyond naming an existing method — they read almost like plain English ('for each element, print it') and reduce visual noise, especially in chained Streams pipelines.
List<String> names = List.of("carl", "Ann", "bob");
// Lambda version:
names.stream().map(s -> s.toUpperCase()).forEach(s -> System.out.println(s));
// Method reference version - reads more directly:
names.stream().map(String::toUpperCase).forEach(System.out::println);Rules and Behavior
A method reference is only valid where the referenced method's signature (parameter types and return type, after accounting for the receiver-as-first-parameter rule for unbound references) is compatible with the target functional interface's abstract method. Overloaded methods are resolved using the same target-typing context as the call site.
A method reference to an instance method captures a 'live' reference to its receiver only for the bound form; for the unbound form, the receiver comes fresh from the functional interface's first argument on every call.
Practical Usage
Extremely common in Streams pipelines (map(Object::toString), forEach(System.out::println)), in Comparator construction (Comparator.comparing(Employee::getSalary)), and anywhere an existing method already does exactly what's needed.
record Employee(String name, double salary) {}
List<Employee> staff = List.of(new Employee("Ann", 50000), new Employee("Bob", 60000));
List<Employee> sorted = staff.stream()
.sorted(Comparator.comparingDouble(Employee::salary))
.toList();Edge Cases
When multiple overloads of the referenced method exist, the compiler picks the one matching the target functional interface's method signature; if the choice is genuinely ambiguous, it's a compile error and requires disambiguation (e.g. an explicit lambda, or a cast).
ClassName::instanceMethod (unbound) is easy to confuse with ClassName::staticMethod (static) — they use identical syntax; the compiler disambiguates based on whether such a static method actually exists and fits the target type, and if both a matching static and a matching instance method exist, the static one is preferred by the resolution rules.
Complete Example
import java.util.*;
import java.util.function.*;
public class MethodReferenceDemo {
static boolean isLong(String s) { return s.length() > 4; }
public static void main(String[] args) {
List<String> words = new ArrayList<>(List.of("kiwi", "banana", "fig", "apple"));
// Static method reference as a Predicate
Predicate<String> longWord = MethodReferenceDemo::isLong;
// Unbound instance method reference for sorting
words.sort(String::compareToIgnoreCase);
// Bound instance method reference for printing
Consumer<String> printer = System.out::println;
words.stream().filter(longWord).forEach(printer);
// banana, apple (order depends on sort, both > 4 chars)
}
}Mental Model
A method reference is a lambda with the 'wiring' already implied by an existing method's name: instead of writing (args) -> doSomething(args), you just point at doSomething directly. It's the same functional-interface machinery underneath — just less to type and read.
Quick Reference
Integer::parseInt // static: s -> Integer.parseInt(s)
obj::someMethod // bound instance: (args) -> obj.someMethod(args)
String::length // unbound instance: s -> s.length()
ArrayList::new // constructor: () -> new ArrayList<>()
Math::max // static, 2-arg: (a, b) -> Math.max(a, b)Key Takeaways
A method reference is shorthand for a lambda that only calls an existing method or constructor, with the same functional-interface target-typing rules underneath.
There are four kinds: static (ClassName::staticMethod), bound instance (instance::method), unbound instance (ClassName::instanceMethod), and constructor (ClassName::new).
In an unbound instance reference, the functional interface's first parameter becomes the method call's receiver.
Constructor references work for regular classes and arrays (int[]::new).
Use a method reference wherever it improves readability over an equivalent lambda; fall back to a lambda when extra logic is genuinely needed.
Function<String, Integer> parse = s -> Integer.parseInt(s); // works but verbose
Function<String, Integer> parse = Integer::parseInt; // clearer method reference
When a lambda's entire body is a single call forwarding its arguments unchanged, a method reference expresses the same thing more directly.
BiFunction<String, String, Boolean> starts = (s, prefix) -> prefix.startsWith(s); // arguments swapped vs intended String::startsWith semantics
BiFunction<String, String, Boolean> starts = String::startsWith; // (s, prefix) -> s.startsWith(prefix)
For an unbound instance method reference, the FIRST functional-interface parameter is always the receiver the method is called on, and the rest are the method's real arguments — mixing this up changes the meaning.
Function<Integer, int[]> maker = size -> new int[size]; // works but a constructor reference is idiomatic here IntFunction<int[]> maker = int[]::new;
Array creation has a dedicated constructor-reference form (Type[]::new); it's the more idiomatic and readable choice when the shape fits, here paired with IntFunction to avoid boxing the size.
What are the four kinds of method references in Java, with their general syntax?
Static (ClassName::staticMethod), bound instance (instance::instanceMethod), unbound instance (ClassName::instanceMethod), and constructor (ClassName::new).
In an unbound instance method reference like String::length used as a Function<String,Integer>, what does the functional interface's single parameter represent?
It represents the receiver object the instance method is called on — the functional interface's parameter 's' becomes 's.length()', i.e. the object the method is invoked against, not an argument passed to the method.
How does a bound instance method reference differ from an unbound one?
A bound reference (instance::method) has its receiver object fixed at the moment the reference is created — every call uses that same object. An unbound reference (ClassName::method) has no fixed receiver; the receiver comes from the functional interface's first parameter on each invocation.
Can a method reference point to a constructor, and if so what's the syntax?
Yes, using ClassName::new, which matches whichever constructor overload fits the target functional interface's parameter list; array types support this too, e.g. int[]::new.
Why might String::compareToIgnoreCase and (a, b) -> a.compareToIgnoreCase(b) be interchangeable as a Comparator<String>?
compareToIgnoreCase is an instance method, so the unbound reference form treats the first Comparator parameter as the receiver ('a') and the second as the method argument ('b'), which is exactly what the equivalent lambda expresses explicitly.
What determines whether the compiler treats ClassName::method as a static or an unbound instance method reference?
It checks whether a matching static method with that name and compatible signature exists on the class; if both a matching static method and a matching instance method exist, resolution rules give preference to the static one, but ideally names shouldn't be ambiguous in the first place.
When would you prefer writing out a full lambda instead of using a method reference?
When the operation needs more than a single forwarding call — extra logic, multiple statements, argument transformation, or conditional behavior — a method reference can't express that; a lambda (or a small named helper method referenced separately) is needed instead.
Write a program that uses method references to sort a List<String> case-insensitively and then print each element.
import java.util.*; public class MethodRefSort { public static void main(String[] args) { List<String> words = new ArrayList<>(List.of("Banana", "apple", "Cherry")); words.sort(String::compareToIgnoreCase); words.forEach(System.out::println); } }
String::compareToIgnoreCase is an unbound instance method reference used as the Comparator; forEach(System.out::println) is a bound instance method reference. Output: apple, Banana, Cherry
What does this print, and why are sb1 and sb2 independent?
hi, then an empty string. StringBuilder::new as a Supplier calls the no-arg constructor fresh each time get() is invoked, so sb1 and sb2 are two distinct StringBuilder instances — mutating one doesn't affect the other.
Write a program that uses a constructor reference to convert a List<String> of names into a List<Employee> (a simple record with just a name field), using stream().map().
import java.util.*; import java.util.stream.*; public class ConstructorRefDemo { record Employee(String name) {} public static void main(String[] args) { List<String> names = List.of("Ann", "Bob", "Carl"); List<Employee> employees = names.stream() .map(Employee::new) .collect(Collectors.toList()); System.out.println(employees); // [Employee[name=Ann], Employee[name=Bob], Employee[name=Carl]] } }
Employee::new is a constructor reference matching Employee(String), used as a Function<String, Employee> inside map(), converting each name into an Employee record instance.
Supplier<StringBuilder> maker = StringBuilder::new;
StringBuilder sb1 = maker.get();
StringBuilder sb2 = maker.get();
sb1.append("hi");
System.out.println(sb1);
System.out.println(sb2);