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

KindExampleEquivalent lambda
StaticInteger::parseInts -> Integer.parseInt(s)
Bound instanceout::printlns -> out.println(s)
Unbound instanceString::lengths -> s.length()
ConstructorArrayList::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::max

Answer: 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" + s

Answer: 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

Pro Tips

📋 Quick Reference

ConceptSyntaxNote
Operator::Forms a method reference
StaticInteger::parseInts -> Integer.parseInt(s)
Bound instanceSystem.out::printlnReceiver fixed
Unbound instanceString::toUpperCaseReceiver = first arg
ConstructorArrayList::newCreates instances
In map.map(Person::name)Field/getter extraction
In forEach.forEach(System.out::println)Print each element
Comparator keyComparator.comparing(P::age)Sort by a field
Collect into typetoCollection(TreeSet::new)Constructor reference
Keep a lambda whenextra logic/argsReference can't express it
SinceJava 8Alongside lambdas

📚 Resources / Keep Learning

🎉 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

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.

Related lessons