Nested & Inner Classes

A class declared inside another class comes in four flavors — static nested, inner, local, and anonymous — each with a different relationship to the enclosing instance and a different best-fit use case.

Java lets a class be declared inside another class, an interface, or even a method. These nested classes come in four distinct forms, distinguished mainly by whether they carry an implicit reference to an enclosing instance and where they can be declared. Picking the right form clarifies both intent and lifetime.

The Four Kinds of Nested Classes

Java recognizes four categories: static nested classes (declared with static, no enclosing-instance link), inner classes (non-static, tied to an enclosing instance), local classes (declared inside a method body), and anonymous classes (declared and instantiated in a single expression, with no name). All four are compiled to their own .class files, distinguished by names like Outer$Inner.class.

Static Nested Class

A static nested class behaves like a top-level class that happens to be namespaced inside another class. It does not hold any implicit reference to an instance of the enclosing class, so it can be instantiated independently — it can only access the enclosing class's static members directly.

class Tree {
    static class Node {
        int value;
        Node left, right;
        Node(int value) { this.value = value; }
    }
}

public class Main {
    public static void main(String[] args) {
        // No Tree instance needed to create a Node:
        Tree.Node node = new Tree.Node(5);
    }
}

Inner (Non-Static) Class

An inner class is declared without static, and every instance of it is implicitly tied to one specific instance of the enclosing class — it can freely access that enclosing instance's fields and methods, including private ones. Creating an inner class instance from outside the enclosing class requires an existing enclosing instance, using the syntax outerInstance.new InnerClass(...).

class Playlist {
    private final java.util.List<String> songs = new java.util.ArrayList<>();

    class Player { // inner class: implicitly tied to a Playlist instance
        void play() {
            for (String song : songs) { // accesses the enclosing Playlist's field directly
                System.out.println("Playing: " + song);
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Playlist playlist = new Playlist();
        Playlist.Player player = playlist.new Player(); // requires an enclosing instance
        player.play();
    }
}

Static Nested vs. Inner: The Core Distinction

A common rule of thumb: if the nested class doesn't need access to the enclosing instance's state, make it static — it avoids an unnecessary hidden reference and clarifies that the nested class is logically independent.

static nested class:
  - no implicit reference to an Outer instance
  - can be created without an existing Outer object
  - can only see Outer's static members directly

inner (non-static) class:
  - holds an implicit reference to one specific Outer instance
  - requires an existing Outer object to be created (outer.new Inner())
  - can see all of Outer's members, static and instance, including private

Local Classes

A local class is declared inside a method (or constructor, or initializer block), scoped only to that block — it is not accessible from outside it. Like an inner class, it can access the enclosing instance's members, and it can also capture local variables and parameters from the enclosing method, provided they are effectively final (never reassigned after initialization).

class ReportBuilder {
    String build(java.util.List<Integer> values) {
        int threshold = 10; // effectively final -- never reassigned

        class Filter { // local class, scoped to this method
            boolean passes(int v) { return v > threshold; }
        }

        Filter filter = new Filter();
        StringBuilder sb = new StringBuilder();
        for (int v : values) {
            if (filter.passes(v)) sb.append(v).append(" ");
        }
        return sb.toString();
    }
}

Anonymous Classes

An anonymous class combines declaration and instantiation into a single expression: it has no name, extends a class or implements an interface implicitly, and can be used exactly once, right where it's created. It is most often used to supply a one-off implementation of a small interface or abstract class inline, without a separate named class.

Since Java 8, a lambda expression is usually more concise than an anonymous class for implementing a functional interface (one with a single abstract method) — see the Lambda Expressions topic. Anonymous classes remain necessary for interfaces/abstract classes with more than one method to implement, or when you need to add extra fields or a constructor-like initializer.

Runnable task = new Runnable() {
    @Override
    public void run() {
        System.out.println("Running inline");
    }
};
task.run();

// Common historical use: an ad-hoc Comparator
java.util.List<String> names = new java.util.ArrayList<>(java.util.List.of("Zed", "Ann"));
names.sort(new java.util.Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.compareTo(b);
    }
});

Access to Enclosing Members

Inner, local, and anonymous classes can all access private members of their enclosing class directly — this is a deliberate part of the language, not a loophole, and is exactly why these forms exist: to let a small helper type act as a genuine extension of the enclosing class's logic while keeping it visually and structurally scoped nearby.

Referring to the Enclosing Instance Explicitly

Inside an inner class, if a field name is shadowed by the inner class's own field of the same name, Outer.this.fieldName explicitly refers to the enclosing instance's field, disambiguating from the inner class's own this.fieldName.

class Outer {
    int value = 1;
    class Inner {
        int value = 2;
        void show() {
            System.out.println(value);       // 2 (Inner's own field)
            System.out.println(this.value);  // 2 (same as above, explicit)
            System.out.println(Outer.this.value); // 1 (Outer's field, explicitly)
        }
    }
}

Effectively Final Capture, Explained

Local and anonymous classes may only capture local variables (and parameters) that are effectively final — meaning they are never reassigned after their initial assignment, even if not explicitly declared final. This restriction exists because the captured variable's value is copied into the nested class instance at creation time; if the original variable could still change afterward, the nested class's copy would silently diverge from it, producing confusing behavior.

int counter = 0;
counter++; // reassigned -- no longer effectively final

Runnable r = () -> System.out.println(counter); // compile error: counter is not effectively final

Use Cases

Static nested classes are a natural fit for small, self-contained helper types tightly associated with an outer class conceptually but not tied to any specific instance — a linked-list Node, a Builder for the outer class, or a small data-carrier used only by the outer class. Inner classes fit when a helper genuinely needs live access to an enclosing instance's changing state — an Iterator implementation for a custom collection is a classic example. Local classes suit a one-off helper needed only within a single method. Anonymous classes suit a small, single-use implementation of an interface or abstract class, especially before lambdas existed, or for interfaces with multiple methods.

Edge Cases

An inner class instance cannot exist without an enclosing instance — attempting outer.new Inner() with a null outer reference throws NullPointerException, and there is no way to construct an inner class instance without going through some enclosing instance.

A static nested class can itself declare static members freely, exactly like a top-level class; an inner (non-static) class cannot declare static members other than compile-time constant fields, because it doesn't have an independent existence apart from its enclosing instance.

An anonymous class cannot have an explicit constructor (it has no name to declare one with), though it can use an instance initializer block for setup logic that would otherwise go in a constructor.

Best Practices

Default to static nested classes unless you specifically need access to a live enclosing instance — this avoids an unnecessary hidden reference (which can also cause subtle memory-retention issues, since an inner class instance keeps its enclosing instance reachable). Prefer lambdas over anonymous classes when implementing a single-method functional interface. Keep local and anonymous classes small; if the logic grows substantial, promote them to a proper named (often static nested) class for readability and reuse.

Complete Example

import java.util.Iterator;
import java.util.NoSuchElementException;

class SimpleStack<T> implements Iterable<T> {
    private Object[] items = new Object[10];
    private int size = 0;

    void push(T item) {
        if (size == items.length) {
            items = java.util.Arrays.copyOf(items, items.length * 2);
        }
        items[size++] = item;
    }

    // Inner class: needs live access to the enclosing SimpleStack's state.
    @Override
    public Iterator<T> iterator() {
        return new Iterator<T>() {
            int cursor = size - 1;
            @Override public boolean hasNext() { return cursor >= 0; }
            @SuppressWarnings("unchecked")
            @Override public T next() {
                if (!hasNext()) throw new NoSuchElementException();
                return (T) items[cursor--];
            }
        };
    }
}

public class Main {
    public static void main(String[] args) {
        SimpleStack<String> stack = new SimpleStack<>();
        stack.push("a");
        stack.push("b");
        stack.push("c");
        for (String s : stack) {
            System.out.println(s); // c, b, a
        }
    }
}

Key Takeaways

Java has four nested-class forms: static nested, inner (non-static), local, and anonymous — distinguished mainly by their relationship to an enclosing instance.

A static nested class has no link to an enclosing instance and can be created without one; an inner class always requires and holds a reference to a specific enclosing instance.

Local classes are scoped to the method they're declared in; anonymous classes combine declaration and instantiation into a single, name-less expression.

Local and anonymous classes may only capture effectively final local variables from their enclosing scope.

Outer.this.member explicitly refers to the enclosing instance's member when a nested class's own member shadows the name.

Default to static nested classes unless live access to the enclosing instance is genuinely required, and prefer lambdas over anonymous classes for single-method functional interfaces.

class Outer { class Node { int value; } // inner class -- unnecessarily tied to an Outer instance } // Every Node now silently carries a hidden reference to its enclosing Outer instance, // even though Node never uses any of Outer's state.

class Outer { static class Node { int value; } // static nested class -- no unnecessary coupling }

When a nested class doesn't need access to the enclosing instance's state, making it static avoids an unnecessary hidden reference to the enclosing instance -- which otherwise adds coupling and can keep the enclosing instance reachable (and unreclaimable by the GC) longer than intended.

class Outer { class Inner { } } public class Main { public static void main(String[] args) { Outer.Inner inner = new Outer.Inner(); // does not compile } }

Outer outer = new Outer(); Outer.Inner inner = outer.new Inner();

An inner (non-static) class instance cannot be created without an existing enclosing instance; it must be created via outerInstance.new Inner(), not directly.

int total = 0; for (int i = 0; i < 5; i++) { total += i; // reassigned repeatedly -- not effectively final } Runnable r = () -> System.out.println(total); // compile error int total = 0; for (int i = 0; i < 5; i++) { total += i; } final int finalTotal = total; // capture a separate, never-reassigned copy Runnable r = () -> System.out.println(finalTotal);

Local and anonymous classes (including lambdas) can only capture local variables that are effectively final. A variable reassigned anywhere in its scope must be copied into a new, never-reassigned variable before it can be captured.

What is the core difference between a static nested class and a (non-static) inner class?

A static nested class has no implicit reference to an instance of the enclosing class and can be created independently. An inner class is implicitly tied to one specific enclosing instance, requires that instance to be created (outer.new Inner()), and can access all of that instance's members, including private ones.

How do you create an instance of a non-static inner class from outside its enclosing class?

Using outerInstance.new InnerClass(...) -- an existing instance of the enclosing class is required.

What kind of local variables can a local class or anonymous class capture from its enclosing method?

Only effectively final local variables and parameters -- ones that are never reassigned after their initial assignment anywhere in their scope, even if not explicitly declared final.

What does Outer.this.field mean when written inside an inner class?

It explicitly refers to the field of the specific enclosing Outer instance the inner class instance is tied to, disambiguating it from a same-named field declared in the inner class itself.

Why can't an anonymous class have an explicit named constructor?

Because it has no name to declare a constructor with -- it is declared and instantiated in a single unnamed expression. It can still use an instance initializer block for setup logic instead.

Since Java 8, what has largely replaced anonymous classes for implementing single-method functional interfaces, and why is it usually preferred?

Lambda expressions. They are typically far more concise than an equivalent anonymous class and clearly communicate that the code is providing just a single piece of behavior, though anonymous classes remain necessary for interfaces with more than one abstract method or when extra state/methods are needed.

Interview-style: why does holding an inner class instance risk keeping its enclosing instance from being garbage collected?

Every inner class instance implicitly holds a reference to its enclosing instance (to access its members). As long as any code holds a reference to the inner class instance, that reference chain keeps the enclosing instance reachable too, which can unintentionally extend the enclosing object's effective lifetime -- one reason to prefer static nested classes when the enclosing instance isn't actually needed.

Write a static nested class LinkedNode<T> inside a class SimpleList<T>, with value and next fields, and demonstrate creating one without a SimpleList instance.

class SimpleList<T> { static class LinkedNode<T> { T value; LinkedNode<T> next; LinkedNode(T value) { this.value = value; } } } public class Main { public static void main(String[] args) { SimpleList.LinkedNode<String> node = new SimpleList.LinkedNode<>("first"); System.out.println(node.value); } }

Because LinkedNode is static, it can be created directly via SimpleList.LinkedNode's constructor without any SimpleList instance existing at all -- confirming it holds no implicit reference to an enclosing SimpleList.

Why does this fail to compile as written, and how would you fix just the access issue (ignore the constructor call syntax, which is correct)?

It fails because Counter.count is private, and Main -- a different top-level class -- cannot access c.count directly. Note that Incrementer itself CAN access count (inner classes can reach the enclosing instance's private members), so the fix is to add a public accessor on Counter, e.g. int count() { return count; }, and call c.count() from Main instead of c.count.

Write a method that returns an anonymous Comparator<String> sorting strings by length, then use it to sort a list.

import java.util.*; public class Main { static Comparator<String> byLength() { return new Comparator<String>() { @Override public int compare(String a, String b) { return Integer.compare(a.length(), b.length()); } }; } public static void main(String[] args) { List<String> words = new ArrayList<>(List.of("banana", "fig", "apple")); words.sort(byLength()); System.out.println(words); // [fig, apple, banana] } }

The anonymous class provides a one-off Comparator<String> implementation inline, with no separate named class needed. It's used exactly once, right where byLength() returns it -- a textbook case for an anonymous class (or equivalently, a lambda, since Comparator is a functional interface).

What does this print, and why?

Prints 5 then 100. Inside Local, the bare name x resolves to the nearest enclosing scope that declares it -- here, the local variable x in demo() (which the local class captures), not Outer's field. Outer.this.x explicitly bypasses that and reaches the Outer instance's own field, which is 100.

class Counter {
    private int count = 0;
    class Incrementer { // inner class
        void increment() { count++; }
    }
}

public class Main {
    public static void main(String[] args) {
        Counter c = new Counter();
        Counter.Incrementer inc = c.new Incrementer();
        inc.increment();
        inc.increment();
        System.out.println(c.count);
    }
}
class Outer {
    int x = 100;
    void demo() {
        int x = 5; // local variable, shadows the field
        class Local {
            void show() {
                System.out.println(x);          // which x?
                System.out.println(Outer.this.x); // which x?
            }
        }
        new Local().show();
    }
}
public class Main {
    public static void main(String[] args) {
        new Outer().demo();
    }
}