Skip to content
Java java8 5 min read

Method References

Method references are a clean shorthand for lambda expressions that simply call an already-existing method. Instead of writing x -> SomeClass.someMethod(x), you write SomeClass::someMethod. Same behavior, less noise.

Why Method References?

When a lambda does nothing but forward its arguments to another method, the lambda itself becomes visual clutter. Method references strip that boilerplate away.

// Lambda version
List<String> names = List.of("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));

// Method reference version — exactly the same behavior
names.forEach(System.out::println);

Both compile to the same bytecode effect. The method reference is just easier to read at a glance.

The Four Kinds of Method References

Java has four flavors of method references. The :: operator is used in all four.

KindSyntaxLambda equivalent
Static methodClassName::staticMethod(args) -> ClassName.staticMethod(args)
Instance method on a specific objectinstance::instanceMethod(args) -> instance.instanceMethod(args)
Instance method on an arbitrary object of a typeClassName::instanceMethod(obj, args) -> obj.instanceMethod(args)
ConstructorClassName::new(args) -> new ClassName(args)

Let’s walk through each one with real examples.

1. Static Method Reference

Use this when the lambda calls a static method, passing its parameter(s) directly as arguments.

import java.util.Arrays;
import java.util.List;

public class StaticRefDemo {
    public static void printUpper(String s) {
        System.out.println(s.toUpperCase());
    }

    public static void main(String[] args) {
        List<String> words = List.of("java", "method", "reference");

        // Lambda
        words.forEach(s -> StaticRefDemo.printUpper(s));

        // Static method reference — cleaner
        words.forEach(StaticRefDemo::printUpper);
    }
}

Output:

JAVA
METHOD
REFERENCE
JAVA
METHOD
REFERENCE

Another common case is Integer::parseInt for conversions:

List<String> numbers = List.of("1", "2", "3");
numbers.stream()
       .map(Integer::parseInt)      // same as s -> Integer.parseInt(s)
       .forEach(System.out::println);

2. Instance Method Reference on a Specific Object

Use this when you have a particular object and the lambda always calls a method on that same object.

public class InstanceRefDemo {
    public static void main(String[] args) {
        String prefix = "Hello, ";

        // Lambda
        List<String> names = List.of("Alice", "Bob");
        names.forEach(name -> System.out.println(prefix.concat(name)));

        // Instance method reference on the specific object 'prefix'
        // prefix::concat takes one argument and returns prefix + that argument
        names.stream()
             .map(prefix::concat)
             .forEach(System.out::println);
    }
}

Output:

Hello, Alice
Hello, Bob
Hello, Alice
Hello, Bob

System.out::println is the most common example of this kind — System.out is the specific PrintStream instance.

3. Instance Method Reference on an Arbitrary Object of a Type

This is the trickiest kind. The first parameter of the lambda becomes the target object, and any remaining parameters become arguments to the method.

import java.util.Arrays;
import java.util.List;

public class ArbitraryRefDemo {
    public static void main(String[] args) {
        List<String> words = List.of("banana", "Apple", "cherry");

        // Lambda: s is the object, compareToIgnoreCase is called on it
        words.sort((a, b) -> a.compareToIgnoreCase(b));

        // Method reference: same thing, String is the type, first arg becomes the receiver
        words.sort(String::compareToIgnoreCase);

        System.out.println(words);
    }
}

Output:

[Apple, banana, cherry]

Another clear example: converting strings to uppercase in a stream.

List<String> upper = List.of("hello", "world")
    .stream()
    .map(String::toUpperCase)   // same as s -> s.toUpperCase()
    .toList();                  // toList() added in Java 16

System.out.println(upper); // [HELLO, WORLD]

Note: String::toUpperCase works here because toUpperCase() takes no arguments — the string itself is the receiver (the “arbitrary instance of type String”).

4. Constructor Reference

Use ClassName::new when a lambda creates a new object. The functional interface’s abstract method signature must match one of the class’s constructors.

import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

public class ConstructorRefDemo {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Charlie");

        // Lambda
        List<StringBuilder> builders1 = names.stream()
            .map(s -> new StringBuilder(s))
            .collect(Collectors.toList());

        // Constructor reference — cleaner
        List<StringBuilder> builders2 = names.stream()
            .map(StringBuilder::new)
            .collect(Collectors.toList());

        builders2.forEach(System.out::println);
    }
}

Output:

Alice
Bob
Charlie

Constructor references are especially handy when working with factories. For instance, ArrayList::new matches Supplier<ArrayList>.

import java.util.ArrayList;
import java.util.function.Supplier;

Supplier<ArrayList<String>> listFactory = ArrayList::new;
ArrayList<String> freshList = listFactory.get();

Practical Examples with Streams

Method references really shine inside stream pipelines.

import java.util.List;
import java.util.stream.Collectors;

public class StreamMethodRefDemo {
    record Person(String name, int age) {}

    public static void main(String[] args) {
        List<Person> people = List.of(
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 35)
        );

        // Extracting names
        List<String> names = people.stream()
            .map(Person::name)          // instance method on arbitrary Person
            .sorted(String::compareTo)  // static-style comparison
            .collect(Collectors.toList());

        System.out.println(names); // [Alice, Bob, Charlie]

        // Printing each name
        names.forEach(System.out::println);
    }
}

Tip: Combine method references with functional interfaces like Predicate, Function, Consumer, and Supplier for maximum expressiveness.

Method References with Comparator

Sorting with method references is particularly readable.

import java.util.Arrays;
import java.util.Comparator;

public class SortDemo {
    public static void main(String[] args) {
        String[] fruits = {"Mango", "apple", "Banana"};

        // Sort alphabetically ignoring case
        Arrays.sort(fruits, String::compareToIgnoreCase);
        System.out.println(Arrays.toString(fruits));

        // Reverse order using Comparator.reversed()
        Arrays.sort(fruits, Comparator.comparing(String::toLowerCase).reversed());
        System.out.println(Arrays.toString(fruits));
    }
}

Output:

[apple, Banana, Mango]
[Mango, Banana, apple]

Under the Hood

At the bytecode level, a method reference compiles to an invokedynamic instruction — the same mechanism used for lambdas. The JVM’s LambdaMetafactory generates a lightweight implementation of the functional interface at runtime, linking it to the target method.

Key points for experienced readers:

  • No extra class file. Unlike anonymous inner classes, neither lambdas nor method references generate a $1.class file at compile time. The actual class is synthesized by LambdaMetafactory at runtime and cached.
  • No capture overhead for unbound references. A reference like String::toUpperCase captures nothing — the JVM creates a stateless singleton-like adapter. A bound reference like System.out::println captures the PrintStream instance, so it has a small allocation, but the JDK JIT typically eliminates this via escape analysis.
  • Identity semantics. Two calls to String::toUpperCase may or may not return the same object (==), so never rely on reference equality of method references.
  • Virtual dispatch is preserved. When you use an instance method reference on an interface or superclass type, the JVM still performs the usual virtual dispatch — it doesn’t devirtualize unless the JIT can prove the type is final.

Note: If you’re coming from the lambda expressions page, think of method references as syntactic sugar layered on top of the same invokedynamic machinery.

Quick Reference Cheat Sheet

// 1. Static
Function<String, Integer> parse = Integer::parseInt;

// 2. Bound instance (specific object)
Consumer<String> print = System.out::println;

// 3. Unbound instance (arbitrary object of a type)
Function<String, String> upper = String::toUpperCase;
Comparator<String> cmp = String::compareToIgnoreCase;

// 4. Constructor
Supplier<StringBuilder> sbFactory = StringBuilder::new;
Function<String, StringBuilder> sbFromStr = StringBuilder::new;

Warning: A method reference only works when the method’s signature (parameter types and return type) matches the abstract method of the target functional interface. If they don’t align, you’ll get a compile-time error — which is actually a feature, not a bug.

  • Lambda Expressions — the foundation that method references build upon
  • Functional Interfaces — the interfaces (Function, Consumer, Predicate, etc.) that method references implement
  • Stream API — where method references are used most heavily in practice
  • Stream Operations (filter/map/reduce) — hands-on stream pipeline patterns using method references
  • Comparator — powerful sorting with method references like Comparator.comparing(Person::name)
  • Collectors — collecting stream results, often combined with constructor references
Last updated June 13, 2026
Was this helpful?