Definition
- Effectively final – a variable that is never reassigned after initialization; the compiler verifies this without requiring the
finalkeyword. - Explicitly final – a variable declared with the
finalkeyword, making the immutability contract visible in source code. - Identical treatment – both produce the same bytecode, both capture the same value copy, and both are legal to capture in lambdas.
- Error location –
finalcatches reassignment at the assignment line; effectively final catches it at the lambda usage line. - Historical – Java 7 anonymous inner classes required explicit
final; Java 8 lambdas relaxed this to accept effectively final.
Two bank employees follow the same rule: never change the interest rate during a transaction. Employee A has a signed policy document (explicit final). Employee B simply never changes the rate (effectively final). The bank trusts both equally. The document just makes it obvious to other employees.
Understanding the Concepts
What is the difference between effectively final and explicitly final?
There is no behavioral difference. The compiler applies the same rule: the variable must not be reassigned after initialization.
package com.bank.service;
import java.math.BigDecimal;
import java.util.List;
public class ThresholdFilter {
// Explicitly final: keyword present
public List<Trade> filterExplicit(List<Trade> trades) {
final BigDecimal threshold = new BigDecimal("100000");
return trades.stream()
.filter(t -> t.getAmount().compareTo(threshold) > 0)
.toList();
}
// Effectively final: no keyword, never reassigned
public List<Trade> filterEffective(List<Trade> trades) {
BigDecimal threshold = new BigDecimal("100000");
// threshold is NEVER reassigned → effectively final
return trades.stream()
.filter(t -> t.getAmount().compareTo(threshold) > 0)
.toList();
}
// BOTH produce identical bytecode and identical results.
}
What happens when you reassign?
// Effectively final LOST:
BigDecimal threshold = new BigDecimal("100000");
threshold = new BigDecimal("200000"); // REASSIGNED!
trades.stream().filter(t -> t.getAmount().compareTo(threshold) > 0);
// COMPILE ERROR: "local variables referenced from a lambda must be final or effectively final"
// Explicit final prevents reassignment earlier:
final BigDecimal threshold = new BigDecimal("100000");
// threshold = new BigDecimal("200000"); // COMPILE ERROR: "cannot assign to final variable"
When should you use explicit final?
// Method parameters: documents the contract
public BigDecimal calculateFee(final Trade trade, final BigDecimal rate) {
return trade.getAmount().multiply(rate);
}
// Loop variables captured by lambdas
for (final Trade trade : trades) {
executor.submit(() -> processTrade(trade));
}
// Configuration values
final BigDecimal MAX_TRANSFER = new BigDecimal("10000000");
When is effectively final sufficient?
// Simple local variables: adding final would be noise
String accountId = request.getAccountId();
trades.stream().filter(t -> t.getAccountId().equals(accountId));
What should a senior engineer know?
- Some teams enforce
finalon all local variables as a coding standard (via IDE or linter rules). - The
finalkeyword on parameters prevents accidental reassignment within the method body, which some consider good practice. - In Java 10+ with
var, you can writefinal var threshold = ...for final type-inferred locals.
What are common interview follow-up questions?
- Does effectively final apply to instance fields? (No. Fields can be reassigned freely. The effectively-final rule applies only to local variables captured by lambdas.)
- Can you capture a non-final variable from an enclosing method? (No. The variable must be effectively final or explicitly final.)
- Why does Java require this? (To prevent seeing stale copies: the lambda captures a value, not a binding.)
Visual Overview
Effectively Final vs Explicitly Final:
final BigDecimal t = 100000; BigDecimal t = 100000;
┌───────────────────────┐ ┌───────────────────────┐
│ Cannot reassign (final│ │ Never reassigned │
│ keyword enforces) │ │ (compiler verifies) │
└───────────┬───────────┘ └───────────┬───────────┘
│ │
▼ ▼
Lambda captures value: 100000 Lambda captures value: 100000
┌───────────────────────┐ ┌───────────────────────┐
│ IDENTICAL bytecode │ │ IDENTICAL bytecode │
│ IDENTICAL behavior │ │ IDENTICAL behavior │
└───────────────────────┘ └───────────────────────┘
Key Takeaway
There is no behavioral difference between an effectively final variable and an explicitly final variable for lambda capture. The compiler treats them identically: both produce the same bytecode, capture the same value copy, and prevent reassignment. The only difference is where the compile error appears: explicit final catches it at the assignment line, effectively final catches it at the lambda line.
Use explicit final for method parameters (documents the contract), configuration constants, and loop variables captured by executor.submit. Effectively final is sufficient for simple local variables that are obviously never reassigned. Historically, Java 7 anonymous inner classes required explicit final. Java 8 lambdas relaxed this to accept effectively final.
Remember one sentence: effectively final and explicitly final are identical for lambda capture; final is documentation, not a different rule.
---