local variables referenced from an inner class must be final or effectively final
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
The Java error "local variables referenced from an inner class must be final or effectively final" appears when an anonymous class, local class, or lambda captures a method-local variable that later changes. The rule exists because Java captures the variable's value, not a live stack slot that can keep mutating after the method returns.
What "Captured" Means
When an inner class uses a local variable from the enclosing method, Java copies that value into the generated object. Consider this example:
This compiles because message is never reassigned. Java treats it as effectively final.
Now compare that with a failing version:
This fails because the lambda captures message, and then the method tries to change it. If Java allowed that, the language would need more complicated semantics for variables that live past the method call and may be shared across threads.
Final Versus Effectively Final
A variable is final if you declare it with the final keyword. A variable is effectively final if you do not reassign it after initialization.
These two examples behave the same:
The second version compiles because timeout never changes. Since Java 8, explicitly writing final is often unnecessary when the variable is already effectively final.
The important limitation is reassignment. Even a small change such as timeout++ breaks effective finality.
Why Java Enforces This Rule
Method-local variables normally live on the stack. An inner class instance may outlive the method call that created it. That means the runtime cannot safely keep a reference to the original local variable in the usual way.
Java solves this by capturing a stable value. Requiring final or effectively final variables guarantees that the captured value does not drift away from what the programmer sees in the source code.
This design also avoids confusing concurrency behavior. If a background thread runs a lambda later, a captured immutable value is much easier to reason about than a mutable local that no longer exists in the original stack frame.
How to Fix the Error
The best fix depends on what you actually want.
If the value should never change, simply stop reassigning it:
If you need mutable state, move that state to an object field:
If you only need a mutable holder inside a method, use a wrapper such as AtomicInteger:
This compiles because the reference to count never changes, even though the object it points to is mutable.
Lambdas and Inner Classes Follow the Same Rule
Many developers first meet this error with lambdas, but the rule is not specific to lambdas. Anonymous classes, local classes, and lambdas all capture local variables using the same basic principle.
That means the following also fails:
The local class Checker captures limit, so reassigning limit is illegal.
Common Pitfalls
One common mistake is assuming "I only change it after the lambda runs" should be allowed. Java checks the code structure, not your intended runtime order.
Another mistake is using a one-element array just to dodge the compiler. That works technically, but it often hides design problems. If the state is truly shared and mutable, an instance field or dedicated holder class is usually clearer.
Developers also confuse object mutation with variable reassignment. Reassigning a captured reference is illegal, but mutating the object behind an unchanged reference can be legal. Whether it is a good idea is a separate design question.
Summary
- Inner classes and lambdas capture local values from the enclosing method.
- Captured locals must be
finalor effectively final. - Reassignment breaks effective finality, even if it happens later in the method.
- Use a field or mutable holder when shared mutable state is really required.
- The rule makes closure behavior simpler, safer, and easier to reason about.

