Compiler error for lambda - "Variable should be effectively final" but no error for method reference

Answered

What is also strange
- There is red errors ‘!’ counter in the editor window top
- After pressing ‘F2’, tooltip is about error
- After moving cursor and returning back, tooltip is “Lambda can be replaced with method reference”

// Example:
// Lambda -> compiler error -> variable should be effectively final.
// Method -> no compiler error.
// But both are just different forms of the same.
class C1 {
    boolean b() {
        return true;
    }
    
    static void b1(BooleanSupplier supplier) {}
    
    static void b2() {
        C1 c1 = null;
        c1 = new C1();
        b1(() -> c1.b()); // lambda - compiler error -> variable should be effectively final
        b1(c1::b); // method - no compiler error
    }
}

0
4 comments

Hi Sammesh 

Could you please share more configuration details of your environment, for example, are you compiling by Gradel and getting the error as well or you're not using Gradle? Do you get this error performing the next steps:

  1. Compile with Lambda
  2. Wait for Compiler Error
  3. Switch to Method Reference
  4. Wait for a Successful Compilation

Which IDE version are you running?

0

Hi Monica ,

I use IntelliJ IDEA 2025.1.3
The project is pom.xml based.
This code is in Scratch file.
When I run/debug scratch, code is compiled with error with Lambda and is compiled and run successfully with Method Reference.

0

Hi Sam Mesh,

Thanks for the details. I reviewed the snipped code you added and searched also through our database. Basically, the compiler’s error is correct because the behavior conforms to the Java spec; the IDE’s suggestion is intentional. So it works as intended. Here's the explanation:

The lambda b1(() -> c1.b()) is illegal because c1 is not effectively final; the method reference b1(c1::b) is legal because it targets the instance captured at invocation time, not at lambda creation time.

So the mixed IDE signals (red error counter, F2 shows error, then “Lambda can be replaced with method reference”) come from the inspection layer seeing a method-reference opportunity even though the lambda is invalid in this context. That suggestion doesn’t imply the lambda is valid; it’s suggesting a refactor that would become valid. Then it works as intended from the Java language perspective.

I reproduced the sample using Java 23, Maven and Scratch file:

• Lambda: b1(() -> c1.b()); → compile error “Variable used in lambda expression should be final or effectively final”.

• Method reference: b1(c1::b); → compiles and runs.

Here's a more detailed explanation of the root cause:

  1. Language semantics:
    • Lambdas capture the value(s) of referenced local variables at the point the lambda is created. Therefore, any local variable referenced by a lambda must be final or effectively final. Here, c1 is reassigned, so it isn’t effectively final → compile-time error.
    • A non-static method reference of the form c1::b is decoded as a function object that, when invoked, calls b on the receiver object captured at the moment the method reference is created. Crucially, the local variable c1 itself is not captured by the method reference expression as a mutable variable; instead, the current value of c1 is evaluated and stored as the receiver. That doesn’t require c1 to be effectively final.
  2. Why the IDE shows both error and “replace with method reference”:
    • The inspection “Lambda can be replaced with method reference” is a structural suggestion that ignores effective-finality constraints. It recognizes that () -> c1.b() is syntactically convertible to c1::b. In this case the conversion also fixes the compilation error, hence the confusing mix of signals. That’s expected; the compiler’s error is correct, and the quick-fix is a valid way to resolve it.
  3. Workarounds / solutions:
    1. Prefer the method reference in this exact pattern: b1(c1::b);
    2. Make the variable effectively final:
      • Assign once: C1 c1 = new C1();
    3. Copy into a final variable before the lambda:

      final C1 c = c1; b1(() -> c.b());

    4. Restructure to avoid capturing a non-final local.

We already have the same case at ticket IDEA-314159, if you want to take a look at it.

0

Hi Monica ,

Thank you for detailed comments, I need some time to digest that this works as specified in JLS. Will comment later.

Even AI might (by mistake?) say that for method local variable should be effectively final.

About https://youtrack.jetbrains.com/issue/IDEA-314159/Method-Reference-causes-Compiler-Error-while-Lambda-Does-Not - this is different stuff. Method reference correctly isn't compiled because method signature is not what is required - provided method is without parameters but required lambda is with one parameter.

0

Please sign in to leave a comment.