Back to Blog
Java

Java Nested Class: Types, Syntax, and Memory Behavior

java nested class: Explains the four kinds of Java nested classes, their syntax, access rules, memory behavior, and when each type is the right choice.

JavaNested ClassesInner ClassesAnonymous ClassesObject-Oriented Design
Diagram showing four types of Java nested classes and their relationship to the enclosing class instance.

What a Java Nested Class Is

A Java nested class is any class declared inside another class. Java defines four kinds of nested classes, and the differences between them are not cosmetic: each kind has different access rules, a different relationship with the enclosing instance, and different memory behavior.

The four kinds are:

  • Static nested class: declared with static, behaves like a top-level class grouped inside another class.
  • Inner class: a non-static nested class that holds an implicit reference to an instance of the enclosing class.
  • Local class: declared inside a method body, scoped to that method.
  • Anonymous class: a local class without a name, declared and instantiated in a single expression.

The term "inner class" is often used loosely to mean any nested class, but Java's language specification reserves it for non-static nested classes. That distinction matters because only inner classes can access the instance members of the enclosing object, and only inner classes keep the enclosing object alive.

Static Nested Classes

A static nested class is declared with the static modifier:

public class Order { private final String id; public Order(String id) { this.id = id; } public static class LineItem { private final String product; private final int quantity; public LineItem(String product, int quantity) { this.product = product; this.quantity = quantity; } public String describe() { return product + " x" + quantity; } } }

Because LineItem is static, it does not hold a reference to an Order instance. You can instantiate it without an existing Order:

Order.LineItem item = new Order.LineItem("USB-C cable", 2);

A static nested class can access static members of the enclosing class, including private ones, but it cannot access instance members because there is no enclosing instance. This makes static nested classes a good way to group a helper type with the class that uses it, without coupling the helper to a particular instance.

Inner Classes and the Enclosing Instance

An inner class is a non-static nested class. It is compiled with an implicit reference to the enclosing instance, which gives it access to that instance's fields and methods, including private ones:

public class ShoppingCart { private final List<Item> items = new ArrayList<>(); public class Item { private final String name; private final double price; public Item(String name, double price) { this.name = name; this.price = price; } public double total() { return price; } } public Item addItem(String name, double price) { Item item = new Item(name, price); items.add(item); return item; } }

The inner class Item can read items and any other field of ShoppingCart even though those fields are private. The reference to the enclosing instance is stored in a synthetic field named this$0 in the compiled class, which the compiler generates for you.

To create an inner class instance, you need an enclosing instance:

ShoppingCart cart = new ShoppingCart(); ShoppingCart.Item item = cart.new Item("Coffee", 4.50);

The syntax cart.new Item(...) makes the relationship explicit. The inner instance keeps the cart instance alive as long as the inner instance itself is reachable.

Because an inner class always has an enclosing instance, it cannot declare static members other than compile-time constants. This is a language rule, not a compiler quirk: a static member would have nowhere to attach without an enclosing instance.

Local Classes

A local class is declared inside a method body. It is scoped to the block in which it is declared, so it cannot be used outside that method:

public class Report { public void printSummary(List<Transaction> transactions) { class Summary { private final double total; Summary() { this.total = transactions.stream() .mapToDouble(Transaction::amount) .sum(); } void print() { System.out.printf("Total: %.2f%n", total); } } new Summary().print(); } }

A local class can access fields and methods of the enclosing class, and it can access local variables and parameters of the method, but only if those variables are effectively final. An effectively final variable is one whose value is never reassigned after initialization. This restriction exists because the local class may outlive the method invocation, and Java captures the variable's value at the point the class is instantiated rather than keeping a live reference to the stack slot.

Local classes are most useful when the logic is specific to one method and does not need to be reused elsewhere. If the same logic appears in multiple methods, a static nested class or a top-level class is usually a better fit.

Anonymous Classes

An anonymous class is a local class without a name. It is declared and instantiated in one expression, typically to provide a one-off implementation of an interface or a subclass:

List<String> names = List.of("alice", "bob", "carol"); names.sort(new Comparator<String>() { @Override public int compare(String a, String b) { return a.length() - b.length(); } });

The syntax is new Type() { ... }, where Type is the interface to implement or the class to extend. The anonymous class body can define fields and methods, but it cannot have a constructor because there is no class name to give it one.

Anonymous classes have the same access rules as local classes: they can access enclosing instance members and effectively final local variables. They are a reasonable choice when an interface has a single implementation that is used in exactly one place. When the same implementation is needed in several places, or when the logic is long enough to hurt readability, a named class is clearer.

Comparing the Four Kinds of Nested Classes

KindDeclared withHolds enclosing instance?Can access enclosing instance members?Instantiation
Static nestedstatic modifierNoStatic members onlynew Outer.Inner()
InnerNo modifierYesYes, including privateouter.new Inner()
LocalInside a methodYesYes, plus effectively final localsWithin the method block
AnonymousIn an expressionYesYes, plus effectively final localsIn the expression itself

The table shows the two properties that drive most decisions: whether the nested class holds an enclosing instance, and what it can access. Static nested classes are the only kind that can be instantiated without an enclosing instance, which makes them the safest default when the nested class does not actually need the enclosing object's state.

Memory and Lifecycle Considerations

The most important operational difference is the enclosing instance reference held by inner, local, and anonymous classes. That reference keeps the enclosing object alive in memory for as long as the nested instance is reachable. If a long-lived collection stores inner class instances, the enclosing objects stay in memory even after they are no longer used elsewhere.

A common failure pattern is registering an inner or anonymous class instance as a listener or callback on a long-lived object, such as a shared event bus or a static cache. The callback holds the enclosing instance, and the enclosing instance may hold other large objects, so the entire object graph stays resident. Using a static nested class for the callback, or passing only the data the callback needs, avoids keeping the enclosing instance alive.

This behavior is a consequence of the language design, not a bug. The reference to the enclosing instance is what gives inner classes access to instance members. If a nested class does not need that access, making it static removes the hidden reference and the associated retention cost.

Choosing the Right Kind of Nested Class

The decision comes down to whether the nested class needs the enclosing instance.

Use a static nested class when the nested type is a helper that does not need the enclosing object's instance state. This is the most common case and the safest default.

Use an inner class when the nested type must read or modify the enclosing instance's fields, and when the two objects have the same lifetime. The implicit reference is then a natural part of the design rather than an accidental retention risk.

Use a local class when the logic is specific to one method and needs access to local variables. If the logic is short and used once, an anonymous class is often more concise.

Use an anonymous class when the implementation is small, single-use, and tied to one call site. If the same implementation appears in more than one place, extract it into a named class.

There is also a practical readability angle. A static nested class is a named, reusable type with a clear constructor. An anonymous class is compact but harder to test in isolation because it has no name and no reusable constructor. For logic that deserves unit tests, a named class, static or top-level, is easier to exercise directly.

Serialization and Compatibility Caveats

Serialization is one area where the kind of nested class changes behavior in ways that are easy to miss. A non-static inner class captures its enclosing instance, so serializing an inner class instance also serializes the enclosing object and everything it references. That can pull an unexpectedly large object graph into the serialized form, and it can fail if the enclosing class is not serializable.

Anonymous and local classes are compiled with synthetic constructors and synthetic fields, which makes their serialized form dependent on compiler-generated details. The Java serialization mechanism requires a matching serialVersionUID, and the synthetic fields must be present in the same order when the class is deserialized. In practice, serializing anonymous or local class instances is fragile and rarely worth the trouble.

Static nested classes do not have this problem: they have no enclosing instance reference, so their serialized form contains only their own fields. If a nested type needs to be serialized, a static nested class is the reliable choice.

The same caution applies to any code that inspects a class reflectively, such as dependency injection containers or mocking frameworks. Synthetic fields and synthetic constructors on inner, local, and anonymous classes can surprise reflection-based tooling. A static nested class has a normal constructor and no synthetic enclosing reference, so it behaves like a top-level class in those tools.

java nested class: Practical Usage and Code Examples | RYUSLOG DEV