skip to content

How does Java infer the types of a lambda's parameters, and when would you write them explicitly or with `var`?

level: middleimportance: should knowfreq 55%

answer

  1. types inferred from the target interface's single method
  2. three styles: inferred / explicit / var
  3. all-or-nothing — no mixing
  4. explicit or var ⇒ parentheses required even for one param
  5. var's killer use: an annotation slot

basics

~20 s

Java usually figures out a lambda's parameter types from the functional interface it's being matched to, so you can leave them off: (a, b) -> a + b. You can write them explicitly like (int a, int b) or use (var a, var b) — but all parameters must use the same style.

solid answer

~40 s

Once target typing fixes the functional interface, the compiler reads that interface's single abstract method to infer each lambda parameter's type, so you normally omit them: `Comparator<String> c = (a, b) -> a.compareTo(b);` infers both as `String`. You may instead write **explicit** types — `(String a, String b) ->` — or use **`var`** parameters (Java 11+) — `(var a, var b) ->`. The choice is **all-or-nothing**: every parameter must be inferred, all explicit, or all `var`; you cannot mix styles. Explicit or `var` parameters always require parentheses, even for one parameter. The main reason to use explicit or `var` types is to **attach annotations** (e.g. `(@NonNull var x) ->`) or to improve readability in a complex lambda; `var` also keeps the inference benefit while giving a syntactic slot for annotations.

go deeper

for a junior

Knows parameter types can usually be omitted and the lambda still compiles.

for a middle

Can state where inferred types come from, the all-or-nothing rule, and the parentheses requirement for explicit/var params.

for a senior

Explains the concrete motivations for var parameters (annotations, readability) and how parameter typing interacts with target typing and overloads.

for a principal

Guides team style on when explicit typing aids maintainability versus noise, and understands the language-design reasons for the all-or-nothing constraint.

## Where parameter types come from A lambda's parameter types are normally **inferred**, not written. The chain is: target typing fixes the **functional interface**, the compiler looks at that interface's **single abstract method**, and copies its parameter types onto the lambda. Example: ```java Comparator<String> byLen = (a, b) -> a.length() - b.length(); ``` `Comparator<String>` has `int compare(String, String)`, so `a` and `b` are inferred as `String` — you call `String` methods on them with no cast. ## Three ways to write parameters 1. **Inferred (the default):** `(a, b) -> ...` — most concise, used most of the time. 2. **Explicit types:** `(String a, String b) -> ...` — you state the types yourself. Useful for documentation or when inference is hard to read. 3. **`var` parameters (Java 11+):** `(var a, var b) -> ...` — the type is still inferred, but each parameter has a `var` keyword. The point of `var` here is **uniformity with local-variable syntax** and, crucially, a **place to hang annotations**. ## The all-or-nothing rule Within a single lambda you must pick one style for **all** parameters: - ✅ `(a, b) -> ...` (all inferred) - ✅ `(int a, int b) -> ...` (all explicit) - ✅ `(var a, var b) -> ...` (all var) - ❌ `(int a, b) -> ...` (mix of explicit + inferred) - ❌ `(var a, int b) -> ...` (mix of var + explicit) This rule keeps the grammar unambiguous. ## Parentheses requirement With inferred types, a single parameter can drop the parentheses: `x -> x.trim()`. But the moment you write an explicit type or `var`, the parentheses become **mandatory**, even for one parameter: `(String x) -> x.trim()` and `(var x) -> x.trim()`. There is no way to write `var x -> ...` or `String x -> ...`. ## Why use explicit or var types at all? Most code uses inference. You reach for explicit or `var` parameters when: - **Annotations** are needed on a parameter — annotations require a type slot: `(@Nonnull var s) -> s.trim()`. - **Readability** — in a long or generically nasty lambda, naming the type can help the reader. - **Disambiguating overloads** — occasionally explicit types steer overload resolution. `var` is the sweet spot when you want the annotation slot without re-stating a verbose generic type. ## Common pitfalls - Mixing styles → compile error. - Dropping parentheses with explicit/`var` types → compile error. - Assuming the type must be written: in the vast majority of cases, omitting it is correct and idiomatic.

  • Why might you choose `(var a, var b) -> ...` over `(a, b) -> ...` when both infer the same types?
    `var` gives each parameter a syntactic slot for annotations (e.g. `(@Nonnull var a, var b) -> ...`), which the bare inferred form cannot carry. It can also read more uniformly with local-variable style. Otherwise the bare form is more concise.
  • Is `(int x) -> x * 2` legal, and is `int x -> x * 2`?
    `(int x) -> x * 2` is legal — explicit types require parentheses. `int x -> x * 2` is illegal: without parentheses you may only use a single inferred parameter.

saying these in an interview costs you the question

  • Mixing inferred and explicit/var parameter types in one lambda
  • Writing `var x -> ...` or `String x -> ...` without parentheses
  • Thinking `var` parameters defeat inference (they don't — the type is still inferred)
  • Believing you must always write parameter types explicitly

context