Method References
Reviewed & published by Brayan K
A method reference, written with the :: operator, is a compact shorthand for a lambda whose only job is to call one existing method — turning s -> obj.print(s) into obj::print.
Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
Before You Start
You should be comfortable with lambda expressions and the basics of the Stream API. Method references have been available since Java 8.
What You'll Learn
- The :: operator and what it produces
- Type::staticMethod references
- object::instanceMethod (bound) references
- Type::instanceMethod (unbound) references
- Type::new constructor references
- When a reference beats a lambda — and when it doesn't
The Big Idea — Point at the Tool, Don't Re-describe It
💡 Analogy: A lambda like s -> System.out.println(s) is you describing, step by step, "take the thing and print it." A method reference is simply pointing at the tool that already does that: System.out::println — "use that printer." When a method to do the job already exists, you don't re-explain it; you just name it.
Method references are not a new capability — every one is exactly equivalent to a lambda. They're a readability feature: shorter to write and, when used well, clearer to read.
1️⃣ From Lambda to Method Reference
If a lambda's body is just one method call that forwards its arguments, swap it for a reference. n -> System.out.println(n) becomes System.out::println; s -> Integer.parseInt(s) becomes Integer::parseInt. Same behaviour, less noise.
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("ada", "linus", "grace");
// A lambda that just calls a method...
names.forEach(n -> System.out.println(n));
System.out.println("---");
// ...is more clearly written as a method reference:
names.forEach(System.out::println); // obj::instanceMethod
System.out.println("--- map with a static method ---");
// Integer::parseInt is Type::staticMethod
List<String> nums = Arrays.asList("10", "20", "30");
int total = nums.stream()
.mapToInt(Integer::parseInt)
.sum();
System.out.println("total = " + total);
}
}2️⃣ The Four Kinds
There are exactly four forms. The trick to telling them apart is asking where the receiver object comes from.
| Kind | Example | Equivalent lambda |
|---|---|---|
| Static | Integer::parseInt | s -> Integer.parseInt(s) |
| Bound instance | out::println | s -> out.println(s) |
| Unbound instance | String::length | s -> s.length() |
| Constructor | ArrayList::new | () -> new ArrayList() |
import java.util.function.Function;
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
// 1) Type::staticMethod
Function<String, Integer> parse = Integer::parseInt;
System.out.println("static : " + parse.apply("42"));
// 2) object::instanceMethod (bound to a specific object)
String prefix = "Hello, ";
Function<String, String> greet = prefix::concat;
System.out.println("bound : " + greet.apply("world"));
// 3) Type::instanceMethod (the first argument is the receiver)
Function<String, Integer> len = String::length;
System.out.println("unbound : " + len.apply("abcde"));
// 4) Type::new (constructor reference)
Supplier<StringBuilder> make = StringBuilder::new;
StringBuilder sb = make.get();
sb.append("built!");
System.out.println("ctor : " + sb);
}
}3️⃣ Method References in Stream Pipelines
This is where references shine. .map(Person::name), .map(String::toUpperCase), Comparator.comparingInt(Person::age), and Collectors.toCollection(ArrayList::new) all read as a clean list of operations rather than a pile of tiny lambdas.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
record Person(String name, int age) {}
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Ada", 36),
new Person("Linus", 54),
new Person("Grace", 85)
);
// Type::instanceMethod inside a stream pipeline (Person::name).
List<String> names = people.stream()
.map(Person::name)
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(names);
// Type::new to collect into a specific list type.
ArrayList<String> copy = names.stream()
.collect(Collectors.toCollection(ArrayList::new));
System.out.println("copy size = " + copy.size());
// Sort with a method-reference comparator key extractor.
List<Person> sorted = new ArrayList<>(people);
sorted.sort(Comparator.comparingInt(Person::age));
sorted.forEach(p -> System.out.println(p.name() + " (" + p.age() + ")"));
}
}4️⃣ Reference or Lambda?
Use a reference when the lambda is a pure pass-through to one method (s -> Main.shout(s) → Main::shout). Keep a lambda the moment you reorder arguments, add a constant, or call more than one method — a reference can't express that, and forcing it hurts clarity.
import java.util.Arrays;
import java.util.List;
public class Main {
// A static helper we can reference.
static String shout(String s) { return s.toUpperCase() + "!"; }
public static void main(String[] args) {
List<String> words = Arrays.asList("hi", "there");
// Lambda vs equivalent method reference, side by side.
words.stream().map(s -> Main.shout(s)).forEach(System.out::println); // lambda
System.out.println("---");
words.stream().map(Main::shout).forEach(System.out::println); // method ref
}
}🧠 Quick Recall
Predict the result before opening each answer.
Math::maxAnswer: Type::staticMethod. max is a static method of Math, so it's a static-method reference equivalent to (a, b) -> Math.max(a, b).
s -> "X" + sAnswer: No. It adds a constant prefix — that's logic beyond a single existing method call, so it must stay a lambda (or you'd write "X"::concat only if the prefix were fixed and concat fit, which it does here as a bound reference). Keep it as a lambda for clarity.
Function<String, Integer> f = String::length;
System.out.println(f.apply("hello"));Answer: 5. String::length is unbound: the argument "hello" becomes the receiver, so it returns "hello".length().
🎯 Your Turn — Trim and upper-case
Chain two unbound (Type::instanceMethod) references in a stream — no lambdas.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<String> words = Arrays.asList(" apple ", "Banana", " cherry");
// YOUR TURN: with TWO unbound method references (Type::instanceMethod),
// trim each word and then upper-case it. No lambdas.
// Expected output:
// [APPLE, BANANA, CHERRY]
List<String> out = words.stream()
// .map(?) // trim
// .map(?) // upper-case
.collect(Collectors.toList());
System.out.println(out);
}
}🧩 Mini-Challenge — A title extractor
Build a Function<Book, String> from the unbound reference Book::title and use it.
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
public class Main {
record Book(String title, int pages) {}
public static void main(String[] args) {
List<Book> books = Arrays.asList(
new Book("Java", 300),
new Book("Streams", 120));
// MINI-CHALLENGE:
// 1. Build a Function<Book, String> using the UNBOUND reference Book::title.
// 2. Print each as "TITLE has N pages" using that function.
//
// Expected output:
// Java has 300 pages
// Streams has 120 pages
// Function<Book, String> titleOf = ...
// books.forEach(b -> System.out.println(...));
}
}Common Errors
- ❌ Forcing a reference when the lambda has extra logic. Fix: keep the lambda — references are only for single pass-through calls.
- ❌ Confusing bound and unbound forms. "hi"::length takes no arg; String::length takes the string as its arg. Fix: match the target interface's parameters.
- ❌ Writing Type.method instead of Type::method. Fix: use the double-colon, not a dot.
- ❌ Ambiguous overloads. If a method name is overloaded incompatibly, the compiler can't pick. Fix: use a lambda, or adjust the target type.
- ❌ Constructor reference for a type with no matching constructor. Fix: ensure a constructor matches the functional interface's parameters.
- ❌ Expecting a reference to capture local variables differently. Capture rules match lambdas. Fix: treat them identically.
Pro Tips
- 💡 Let your IDE suggest the conversion. Most editors offer to replace an eligible lambda with a method reference.
- 💡 Comparator.comparing(Person::name) is the idiomatic way to sort by a field — far cleaner than a hand-written comparator.
- 💡 Constructor references power factories and Collectors.toCollection(TreeSet::new)-style customisation.
- 💡 Readability first. If the reference makes a reader hunt for what it means, a small lambda may be the kinder choice.
📋 Quick Reference
| Concept | Syntax | Note |
|---|---|---|
| Operator | :: | Forms a method reference |
| Static | Integer::parseInt | s -> Integer.parseInt(s) |
| Bound instance | System.out::println | Receiver fixed |
| Unbound instance | String::toUpperCase | Receiver = first arg |
| Constructor | ArrayList::new | Creates instances |
| In map | .map(Person::name) | Field/getter extraction |
| In forEach | .forEach(System.out::println) | Print each element |
| Comparator key | Comparator.comparing(P::age) | Sort by a field |
| Collect into type | toCollection(TreeSet::new) | Constructor reference |
| Keep a lambda when | extra logic/args | Reference can't express it |
| Since | Java 8 | Alongside lambdas |
📚 Resources / Keep Learning
- 📘 Oracle Tutorial: Method References — the four kinds explained.
- 📗 dev.java: Method References — a guided tutorial.
- 📙 java.util.function API — the interfaces references target.
🎉 Lesson Complete!
You can now read and write all four kinds of method reference — Type::staticMethod, obj::instanceMethod, Type::instanceMethod, and Type::new — use them throughout stream pipelines, and judge when a lambda is the better choice.
Practice quiz
What is a method reference?
- A pointer to memory
- A compact shorthand for a lambda that just calls one method
- A way to override a method
- A reflection API call
Answer: A compact shorthand for a lambda that just calls one method. A method reference (Type::method) is shorthand for a lambda whose body is a single method call.
Which operator forms a method reference?
- .
- ->
- ::
- =>
Answer: ::. Method references use the double-colon :: between the type/object and the method name.
Integer::parseInt is which kind of reference?
- object::instanceMethod
- Type::staticMethod
- Type::instanceMethod
- Type::new
Answer: Type::staticMethod. parseInt is static, so Integer::parseInt is a reference to a static method.
System.out::println is which kind?
- Type::staticMethod
- object::instanceMethod (bound)
- Type::instanceMethod (unbound)
- Type::new
Answer: object::instanceMethod (bound). It calls println on the specific object System.out, so it is a bound instance-method reference.
String::length is which kind?
- Type::staticMethod
- object::instanceMethod
- Type::instanceMethod (the first arg is the receiver)
- Type::new
Answer: Type::instanceMethod (the first arg is the receiver). String::length is unbound: the receiver is supplied as the first argument at call time.
ArrayList::new is which kind?
- Type::new (constructor reference)
- Type::staticMethod
- object::instanceMethod
- Type::instanceMethod
Answer: Type::new (constructor reference). ::new is a constructor reference, creating a new instance.
When can you replace a lambda with a method reference?
- Always
- When the lambda body is just a single call to an existing method with matching arguments
- Never in streams
- Only for static methods
Answer: When the lambda body is just a single call to an existing method with matching arguments. If the lambda simply forwards its arguments to one method, a method reference is the cleaner equivalent.
What is the difference between obj::m and Type::m for an instance method?
- None
- obj::m is bound to that object; Type::m takes the receiver as the first argument
- Type::m is faster
- obj::m is static
Answer: obj::m is bound to that object; Type::m takes the receiver as the first argument. A bound reference fixes the receiver; an unbound one expects the receiver as the first parameter.
Is s -> s.toUpperCase() equivalent to String::toUpperCase?
- Yes
- No
- Only for static
- Only in forEach
Answer: Yes. Both turn one String argument into its upper-cased form; String::toUpperCase is the unbound form.
Why prefer method references over lambdas where possible?
- They run on the GPU
- They are shorter and often read more clearly as intent
- They avoid exceptions
- They are required by streams
Answer: They are shorter and often read more clearly as intent. When applicable, a method reference is more concise and states 'apply this method' directly.
Continue this course
- Previous: Record Patterns
- Next: Functional Interfaces — Function, Supplier, Consumer, Predicate and writing your own @FunctionalInterface
- Quick reference: Java cheat sheet
Frequently asked questions
What is a method reference and when is it equivalent to a lambda?
A method reference, written Type::method or object::method, is a compact way to write a lambda whose entire body is a single call to an existing method. Whenever a lambda just forwards its parameters to one method, you can usually replace it with a method reference: s -> System.out.println(s) becomes System.out::println, and s -> Integer.parseInt(s) becomes Integer::parseInt. The two compile to the same functional-interface instance; the reference is simply shorter and often reads as clearer intent.
What are the four kinds of method reference?
1) Type::staticMethod refers to a static method, e.g. Integer::parseInt. 2) object::instanceMethod is bound to a specific object, e.g. System.out::println always prints to that stream. 3) Type::instanceMethod is unbound — the receiver is supplied as the first argument at call time, e.g. String::length called on whatever string is passed in. 4) Type::new is a constructor reference, e.g. ArrayList::new creates new instances. Recognising which kind you need comes down to where the receiver object comes from.
What is the difference between obj::method and Type::method for instance methods?
A bound reference like prefix::concat fixes the receiver to a particular object (prefix); calling it supplies only the method's other arguments. An unbound reference like String::length leaves the receiver open: the first argument you pass becomes the object the method runs on. So String::length behaves like (String s) -> s.length(), while "hi"::length behaves like () -> "hi".length(). The same method name can appear in both forms depending on whether the receiver is fixed.
When should I prefer a method reference over a lambda?
Prefer a method reference when the lambda's body is literally one method call with the arguments passed straight through — that is exactly the case a reference expresses most cleanly (Person::name reads better than p -> p.name()). Keep a lambda when you do anything more: rearranging arguments, adding logic, calling several methods, or supplying extra constants. The goal is readability, so don't twist code into a reference if a short lambda is clearer.
Can method references be used anywhere a functional interface is expected?
Yes. A method reference, like a lambda, is a way to provide an instance of a functional interface — any interface with a single abstract method. That means they work as arguments to stream operations (map, filter, forEach), as Comparators (Comparator.comparing(Person::name)), as Suppliers, Consumers, Functions, and so on. The compiler checks that the referenced method's signature is compatible with the interface's single abstract method, just as it does for a lambda.