Java Method References Explained with Examples

A Java method reference names an existing method or constructor where a functional interface is expected.

Lesson 82 of 127Advanced13 min read

A Java method reference names an existing method or constructor where a functional interface is expected. It is a shorter alternative to a lambda that only forwards its arguments. System.out::println, Integer::parseInt, and ArrayList::new are familiar forms, but their meanings depend on the target interface. Read a method reference by first asking what arguments the interface provides and which method consumes them. It does not call the method at the point where you write the reference.

Static method references

A static reference uses ClassName::methodName. Integer::parseInt can implement Function<String, Integer> because parseInt accepts a String and returns an int that can be boxed into Integer. The equivalent lambda is text -> Integer.parseInt(text). This form is useful when the method already describes the operation clearly. If you must adjust the argument, validate it first, or combine several calculations, a lambda usually communicates that extra work more honestly.

References bound to a particular object

An instance reference such as prefix::concat stores the receiver object when the reference is created. Later interface calls provide only the method arguments. If prefix is "Gate ", Function<String, String> can use prefix::concat to append each supplied suffix. A null receiver causes NullPointerException while the bound reference is evaluated. This timing differs from a lambda that accesses the receiver later, so do not treat every replacement as identical in programs with changing state.

References to methods on an input object

String::toUpperCase is an unbound instance method reference. No particular string is stored as the receiver. Instead, the functional interface's first argument becomes the receiver when the method is invoked. Function<String, String> supplies that string. For a method with another argument, the target must supply both the receiver and the method argument: String::startsWith can fit BiPredicate<String, String>. Count the receiver when checking whether the interface shape matches.

Constructor references

ClassName::new requests object construction when the functional method executes. Supplier<ArrayList<String>> can use ArrayList::new because its get method supplies no arguments and expects an ArrayList result. A constructor that accepts an int can instead fit IntFunction for creating an object with a supplied size. Constructor references do not create the object immediately. In the example, each supplier call returns a different list, so adding to one does not change the other.

Overloads and compiler inference

Methods such as println have several overloads. The target type helps Java choose the applicable one. Consumer<String> selects an overload compatible with String input, while a different interface can select another overload. When the surrounding expression does not provide enough information, declare the interface type or use a lambda with an explicit parameter type. A reference cannot repair an incompatible signature; its syntax is shorter, but ordinary type checking still applies.

Choosing between a reference and a lambda

Use a method reference when it makes the operation easier to recognize. labels.stream().map(String::trim) clearly trims each input. A lambda such as label -> label.trim().toLowerCase(Locale.ROOT) makes two operations visible and should not be forced into an obscure helper solely to obtain a double colon. A reference also preserves the referenced method's exception behavior. It adds no automatic validation, retries, or special treatment for null arguments.

Practice tasks and checks

Rewrite each reference as an equivalent lambda and compare the output. Integer::parseInt becomes text -> Integer.parseInt(text), while heading::concat becomes suffix -> heading.concat(suffix). The receiver in String::length must come from the lambda parameter, so use text -> text.length(). For the constructor reference, write () -> new ArrayList<String>(). Then add a fourth operation that parses a number and adds ten. Use a lambda for that combined operation rather than trying to attach arithmetic to a method reference. Test two valid numeric strings and one invalid string, describing the exception separately from the successful outputs. The exercise makes receiver binding and argument forwarding concrete rather than treating the double colon as decorative syntax.

Java Summary

  • A bound reference stores its receiver; an unbound reference receives it as an argument.
  • Constructor references construct objects when the functional method is invoked.
  • Use a lambda when the operation needs extra calculation or decisions.
  • Continue with stream pipelines when these examples are clear.

Java Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Calling an existing static parser

Save this program as StaticReference.java. Compile it with javac StaticReference.java and run it with java StaticReference.

Expected output:

The assignment creates a function whose apply operation calls Integer.parseInt. Passing the string twenty-seven produces the integer twenty-seven; the surrounding addition then adds three. Without parsing, adding text and a number could perform string concatenation instead. The explicit Function type tells the compiler which signature the reference must satisfy. The parser still rejects nonnumeric text with NumberFormatException.

Java
import java.util.function.Function;
public class StaticReference {
    public static void main(String[] args) {
        Function<String, Integer> number = Integer::parseInt;
        System.out.println(number.apply("27") + 3);
    }
}
Output
30
Example 2

Comparing bound and unbound receivers

Save this program as ReceiverReferences.java. Compile it with javac ReceiverReferences.java and run it with java ReceiverReferences.

Expected output:

addHeading is bound to the heading object, so its argument becomes the suffix passed to concat. length is unbound: the argument Seven becomes the String on which length is called. The first result is a newly concatenated string, while the second is its input's character count. Neither operation modifies a String because strings are immutable. The receiver distinction explains why both references need one argument despite referring to differently shaped methods.

Java
import java.util.function.Function;
public class ReceiverReferences {
    public static void main(String[] args) {
        String heading = "Dock ";
        Function<String, String> addHeading = heading::concat;
        Function<String, Integer> length = String::length;
        System.out.println(addHeading.apply("Seven"));
        System.out.println(length.apply("Seven"));
    }
}
Output
Dock Seven
5
Example 3

Creating separate collections

Save this program as ListFactory.java. Compile it with javac ListFactory.java and run it with java ListFactory.

Expected output:

Each get call invokes the ArrayList constructor anew. first and second therefore refer to distinct collection objects. Adding Lantern through first leaves second empty. A supplier written as () -> first would behave differently because it would return an existing list rather than constructing one. The constructor reference states the creation operation, while the Supplier interface determines when callers can request it.

Java
import java.util.ArrayList;
import java.util.function.Supplier;
public class ListFactory {
    public static void main(String[] args) {
        Supplier<ArrayList<String>> factory = ArrayList::new;
        ArrayList<String> first = factory.get();
        ArrayList<String> second = factory.get();
        first.add("Lantern");
        System.out.println(first);
        System.out.println(second);
    }
}
Output
[Lantern]
[]

Java FAQ

What does the double colon mean?

It introduces a method or constructor reference. It does not mean execute now. The receiving functional interface provides the eventual invocation method and arguments, and the referenced member supplies that invocation's implementation.

Can a method reference contain extra arithmetic?

No. Integer::parseInt + 3 is not a method reference that adjusts a result. Use a lambda such as text -> Integer.parseInt(text) + 3, or compose a parser function with a second transformation.

Why does System.out::println work with forEach?

System.out is a specific PrintStream object, so System.out::println is a bound method reference: each element passed by forEach becomes the argument to println on that object. It's equivalent to x -> System.out.println(x).

Can a method reference create an array?

Yes. String[]::new is a reference to an array constructor that takes a length. It's most often used with stream.toArray(String[]::new), which gives a correctly typed String[] instead of an Object[].

Are method references faster than lambdas?

No meaningful difference. Both compile to similar code and the JVM optimises them the same way. Choose whichever reads more clearly: a method reference for a direct call to an existing method, a lambda when there's any extra logic.

Java Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Multiple choiceeasy

In String::length assigned to Function<String, Integer>, what becomes the receiver?

Multiple choicemedium

When does ArrayList::new assigned to Supplier create a list?

Java Quiz

2 questions to check what you learned.

  1. Which reference fits a String-to-integer parser?

    • Integer::parseInt
    • String::concat
    • System.out::println
  2. What does a method reference do with exceptions thrown by its method?

    • They follow the ordinary invocation behavior
    • They are automatically converted to zero
    • They are always caught by the interface