Back to Blog
Java

Java Lambda Multiple Parameters: Syntax and Usage

Learn how to write Java lambda expressions with multiple parameters: syntax rules, functional interfaces, type inference, common mistakes, and practical patterns.

lambda expressionsfunctional interfacesBiFunctiontype inferencemethod referencesJava syntax
Diagram showing two input arrows converging into a lambda symbol and then flowing into a single output arrow.

To write a Java lambda with multiple parameters, put the parameter list in parentheses. For example:

(int left, int right) -> left + right

When the target type supplies enough information, you can omit the parameter types:

(left, right) -> left + right

The parentheses are mandatory when there are two or more parameters. A single inferred parameter can omit them, but the moment you add a second parameter, the parentheses become required. This is the first rule most developers hit when moving from single-parameter lambdas to multi-parameter ones.

Functional Interfaces That Accept Multiple Arguments

A lambda expression is valid only where the target type is a functional interface. A functional interface has exactly one abstract method, and the lambda's parameter list must match that method's signature.

The standard library provides several functional interfaces for two parameters:

InterfaceMethodReturns
BiFunction<T,U,R>apply(T t, U u)R
BinaryOperator<T>apply(T t, T u)T
BiPredicate<T,U>test(T t, U u)boolean
BiConsumer<T,U>accept(T t, U u)void

For example:

BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b; int result = add.apply(3, 4);

The lambda (a, b) -> a + b matches apply(Integer, Integer) because the compiler infers a and b as Integer from the target type BiFunction<Integer, Integer, Integer>.

Type Inference vs Explicit Parameter Types

When the target type is known, you can write:

BiPredicate<String, String> startsWith = (s, prefix) -> s.startsWith(prefix);

The compiler infers s and prefix as String. If you prefer explicit types, the parentheses must be retained:

BiPredicate<String, String> startsWith = (String s, String prefix) -> s.startsWith(prefix);

You cannot mix inferred and explicit types in the same parameter list. Either every parameter declares its type, or none do. This is a compile-time error:

// Does not compile BiFunction<Integer, Integer, Integer> bad = (Integer a, b) -> a + b;

Common Mistakes When Writing Multi-Parameter Lambdas

The most frequent error is omitting the parentheses:

// Does not compile BiFunction<Integer, Integer, Integer> wrong = a, b -> a + b;

The compiler reports a syntax error because a lambda with two parameters requires the parenthesized parameter list.

Another common mistake is a parameter count mismatch. The lambda must accept exactly the number of parameters declared by the functional interface's abstract method. If BiFunction expects two arguments, the lambda must declare two parameters. Using one parameter or three parameters fails at compile time.

A subtler issue arises when the lambda body produces a value but the functional interface method returns void. For example, BiConsumer expects void, so a body like (a, b) -> a + b is invalid because that expression is not a statement expression. The compiler will reject it.

Practical Patterns: BiFunction and Custom Interfaces

BiFunction covers two parameters, but Java's standard library stops there. There is no built-in TriFunction. When you need three or more parameters, define your own functional interface:

@FunctionalInterface interface TriFunction<A, B, C, R> { R apply(A a, B b, C c); } TriFunction<Integer, Integer, Integer, Integer> sum3 = (a, b, c) -> a + b + c;

The @FunctionalInterface annotation is not required, but it causes a compile-time error if the interface accidentally declares more than one abstract method. That check is valuable when the interface is part of a shared codebase.

For two parameters, prefer the standard interfaces over custom ones. BiFunction, BinaryOperator, BiPredicate, and BiConsumer cover the common cases, and using them keeps the API surface familiar.

BinaryOperator<T> is a specialization of BiFunction<T,T,T>. Use it when both arguments and the return value share the same type:

BinaryOperator<Integer> max = (a, b) -> Math.max(a, b);

Runtime Behavior and Performance Considerations

A multi-parameter lambda compiles to an invokedynamic call site. At runtime, the JVM links the lambda to a synthetic method and, for a non-capturing lambda, can reuse a single instance. A non-capturing lambda does not reference any values from the enclosing scope, such as local variables, method parameters, or this, so the JVM can cache the instance and avoid allocating a new object on every evaluation.

A capturing lambda references values from the enclosing scope, so each evaluation creates a new instance because the lambda object must store those values. This applies to multi-parameter lambdas exactly as it does to single-parameter ones; the number of parameters does not change the allocation behavior.

For most application code, this distinction is irrelevant. Lambda allocation is cheap, and the JIT compiler can often inline the synthetic method. The practical concern appears in hot loops where a capturing lambda is created millions of times. If profiling shows allocation pressure, consider whether the lambda can be made non-capturing or whether the logic belongs in a regular method.

When Multiple Parameters Are Not Enough

A lambda with many parameters becomes hard to read, and the meaning of each positional argument is not obvious. When the parameter list grows, consider introducing a small record (available in Java 16 and later) or a dedicated class to group the values:

record Point(int x, int y) {} BiFunction<Point, Point, Double> distance = (p1, p2) -> Math.hypot(p1.x() - p2.x(), p1.y() - p2.y());

This keeps the lambda readable and gives the parameters meaningful names through the record's accessor methods. The tradeoff is an extra type, but for a lambda that appears in multiple places, the clarity is usually worth it.

Method references can also replace a multi-parameter lambda when the body is a single existing method call:

BiFunction<String, String, String> concat = String::concat;

This is equivalent to (a, b) -> a.concat(b). Use a method reference when it expresses the intent more directly; keep the lambda form when the body involves logic beyond a single call.

Java Lambda Multiple Parameters: Syntax, Type Inference, and Examples | RYUSLOG DEV