Local Inner Class
A local inner class is a class you declare inside a method body — or any other block like a constructor, an if statement, or a for loop. It exists only within that block, it can access the enclosing class’s members, and it can capture local variables from the surrounding method as long as those variables are effectively final.
What Is a Local Inner Class?
Think of a local inner class as a tiny helper class that lives inside a single method. No other method in the program can even see it, let alone instantiate it. This tight scoping makes it perfect when you need a class-like structure (multiple methods, state) for a task that is entirely self-contained within one method.
class Greeting {
void sayHello(String language) {
// Local inner class — only visible inside sayHello()
class Message {
void print() {
if (language.equals("Spanish")) {
System.out.println("¡Hola!");
} else {
System.out.println("Hello!");
}
}
}
Message msg = new Message();
msg.print();
}
}
public class Main {
public static void main(String[] args) {
Greeting g = new Greeting();
g.sayHello("Spanish");
g.sayHello("English");
}
}
Output:
¡Hola!
Hello!
Note: The
Messageclass is declared inside thesayHellomethod. You cannot writenew Greeting.Message()anywhere outside that method — the compiler simply does not know about it there.
Accessing Outer Class Members
Just like a member inner class, a local inner class can freely access all members of the enclosing outer class — including private fields and methods — without any qualifier.
class Counter {
private int count = 0;
void runTwice() {
class Incrementor {
void increment() {
count++; // directly modifies Counter's private field
System.out.println("Count is now: " + count);
}
}
Incrementor inc = new Incrementor();
inc.increment();
inc.increment();
}
}
public class Main {
public static void main(String[] args) {
new Counter().runTwice();
}
}
Output:
Count is now: 1
Count is now: 2
Capturing Local Variables (Effectively Final)
A local inner class can also read local variables from the surrounding method, but there is a strict rule: those variables must be effectively final — meaning they are never reassigned after their initial assignment.
class Report {
void generate(String title, int pageCount) {
// Both 'title' and 'pageCount' are effectively final here
class Section {
void print() {
System.out.println("Report : " + title);
System.out.println("Pages : " + pageCount);
}
}
new Section().print();
}
}
public class Main {
public static void main(String[] args) {
new Report().generate("Annual Summary", 42);
}
}
Output:
Report : Annual Summary
Pages : 42
If you try to reassign title or pageCount after the local class uses them, the compiler will refuse:
void generate(String title) {
title = "Changed"; // ← reassignment makes 'title' NOT effectively final
class Section {
void print() {
System.out.println(title); // Compile error!
}
}
}
Warning: The error message says “local variables referenced from an inner class must be final or effectively final.” This applies to both
final-declared variables and variables that are simply never reassigned — Java 8+ removed the requirement to writefinalexplicitly, as long as you don’t reassign.
Implementing an Interface
A local inner class can implement an interface or extend a class, which makes it more flexible than an anonymous inner class when you need to implement several methods or reuse the same implementation in multiple places within the method.
interface Shape {
double area();
double perimeter();
}
class Geometry {
void describe(double side) {
class Square implements Shape {
@Override
public double area() {
return side * side;
}
@Override
public double perimeter() {
return 4 * side;
}
}
Shape sq = new Square();
System.out.println("Area : " + sq.area());
System.out.println("Perimeter : " + sq.perimeter());
}
}
public class Main {
public static void main(String[] args) {
new Geometry().describe(5.0);
}
}
Output:
Area : 25.0
Perimeter : 20.0
Tip: When a local inner class implements only one method, consider replacing it with a lambda expression or an anonymous inner class. Keep local inner classes for multi-method implementations where a name improves readability.
Rules and Restrictions
Local inner classes have a few important constraints:
| Rule | Detail |
|---|---|
| Scope | Only visible inside the block where declared |
| Access to outer class | Full access, including private members |
| Access to local variables | Yes, but they must be effectively final |
| Access modifiers | Cannot be public, protected, private, or static |
static members | Cannot declare static fields or methods (before Java 16) |
| Instantiation | Must be instantiated within the same block |
Note: From Java 16 onward, local classes may declare
staticmembers (as part of the general relaxation of static member restrictions). If you target Java 8–15, avoidstaticmembers inside local classes.
Comparison: Local vs Anonymous vs Member Inner Class
| Feature | Local Inner Class | Anonymous Inner Class | Member Inner Class |
|---|---|---|---|
| Has a name | Yes | No | Yes |
| Where declared | Inside a method/block | Inline in an expression | Inside class body |
| Can be instantiated multiple times? | Yes (within the block) | No (once, on the spot) | Yes (from outer instance) |
| Implements multiple methods | Yes | Yes | Yes |
| Access to local variables | Effectively final only | Effectively final only | N/A |
| Typical use case | Named, multi-method helper scoped to a method | Quick one-off implementation | Persistent helper tied to outer instance |
Under the Hood
When the compiler processes a local inner class, it generates a separate .class file just as it does for member inner classes. For a local class named Message inside a method of Greeting, the output file is named something like Greeting$1Message.class — the number disambiguates multiple local classes with the same name in the same outer class.
Captured variables are copied, not referenced. When your local class uses an effectively final local variable, the compiler creates a synthetic constructor parameter and copies the value into a hidden field inside the local class. This is why the variable must not change after the copy is made — if it did, the inner class would be working with a stale snapshot.
You can inspect this with the javap tool:
javac Greeting.java
javap -p "Greeting\$1Message"
You will see a synthetic field holding the captured value, and a constructor that accepts it as a parameter.
Outer reference. Like a member inner class, a local inner class also holds a hidden this$0 reference to the enclosing outer instance (unless the method is static). This means the outer instance cannot be garbage-collected while a local class instance is reachable — the same memory-leak caveat that applies to member inner classes. See Garbage Collection Deep-Dive for how the JVM reclaims objects.
Tip: Because local inner classes are scoped to a method, they naturally have short lifetimes. Memory leaks from local classes are rare in practice — but if you store a local class instance in a long-lived data structure, all the usual warnings apply.
Quick Summary
- Declare a local inner class inside any block (method, constructor,
if, loop). - It is invisible outside that block — use it only for method-local complexity.
- It can access all outer class members and any effectively final local variables.
- It cannot have access modifiers (
public,private, etc.) or bestatic. - The compiler emits a
Outer$NName.classfile and copies captured variables into hidden synthetic fields. - Prefer an anonymous inner class for one-liners, a lambda for single-method functional interfaces, and a member inner class when the helper is needed across multiple methods.
Related Topics
- Inner Classes — overview of all four kinds of nested classes and when to choose each
- Member Inner Class — the non-static class declared at the class level, with full outer-instance access
- Anonymous Inner Class — unnamed one-off implementations; often the shorter alternative to local inner classes
- Lambda Expressions — the modern replacement for single-method local or anonymous classes
- Garbage Collection Deep-Dive — understand why hidden outer references in inner classes can prevent garbage collection
- Access Modifiers — local classes cannot use access modifiers; here’s why that matters