Skip to content
Java inner classes 6 min read

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 Message class is declared inside the sayHello method. You cannot write new 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 write final explicitly, 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:

RuleDetail
ScopeOnly visible inside the block where declared
Access to outer classFull access, including private members
Access to local variablesYes, but they must be effectively final
Access modifiersCannot be public, protected, private, or static
static membersCannot declare static fields or methods (before Java 16)
InstantiationMust be instantiated within the same block

Note: From Java 16 onward, local classes may declare static members (as part of the general relaxation of static member restrictions). If you target Java 8–15, avoid static members inside local classes.

Comparison: Local vs Anonymous vs Member Inner Class

FeatureLocal Inner ClassAnonymous Inner ClassMember Inner Class
Has a nameYesNoYes
Where declaredInside a method/blockInline in an expressionInside class body
Can be instantiated multiple times?Yes (within the block)No (once, on the spot)Yes (from outer instance)
Implements multiple methodsYesYesYes
Access to local variablesEffectively final onlyEffectively final onlyN/A
Typical use caseNamed, multi-method helper scoped to a methodQuick one-off implementationPersistent 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 be static.
  • The compiler emits a Outer$NName.class file 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.
  • 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
Last updated June 13, 2026
Was this helpful?