The first time I saw a class written inside another class, I thought it was a typo. Then I saw new Comparator<String>() { ... } with curly braces right after a constructor call, and I was sure my code editor was broken.
It wasn't. Java lets you put classes inside other classes, and it has several flavors of this: inner classes, static nested classes, local classes and anonymous classes. They look similar, but they behave quite differently. The biggest difference is something hidden that you never write: a secret link between the inner object and its outer object.
In this post, we'll look at each kind, see what the compiler really produces, and learn which one to choose. If you've read about abstract classes and interfaces, you'll recognize the building blocks.
Unless I show a full class, the snippets go inside a main method.
Why put a class inside another class?
A class that lives inside another class is called a nested class. People do it for a few good reasons:
-
Grouping. If a class is only useful to one other class, keep them together. A
Nodefor a linked list belongs with the list. - Encapsulation. A nested class can use the private members of the class around it, and you can hide the nested class from the rest of the world.
- Readability. The helper sits right next to the code that uses it.
Here is the family tree. Learn this shape first, because everything else hangs on it:
Nested classes (classes written inside classes)
|
|-- static nested class (has the keyword "static")
| no outer object needed
|
|-- inner classes (no "static")
| always tied to an outer object
|-- member inner class (written in the class body)
|-- local inner class (written inside a method)
|-- anonymous inner class (no name, created with "new")
The word "inner class" is used loosely in casual talk, but strictly speaking, inner means non-static.
Member inner class
A member inner class is a plain class declared inside another class, without static.
class Car {
private String brand = "Tata";
class Engine {
void start() {
System.out.println(brand + " engine started");
}
}
}
Look at start(). It uses brand, which is a private field of Car. An inner class can do that, because it's considered part of the outer class.
To create an inner object, you first need an outer object:
Car car = new Car();
Car.Engine engine = car.new Engine();
engine.start();
Output:
Tata engine started
The odd-looking car.new Engine() says: "create an Engine that belongs to this car." Every inner object belongs to one specific outer object. Two different cars would give you two engines that each use their own brand.
If you try to create it without an outer object:
Car.Engine e = new Car.Engine();
an enclosing instance that contains Car.Engine is required
Behind the scenes: the hidden reference
The compiler doesn't have a special "inner" feature in the JVM. It turns your inner class into a normal separate class with a funny name, and it quietly adds a hidden field that points to the outer object.
Files created by javac:
Car.class
Car$Engine.class <- the inner class gets its own file, named Outer$Inner
At run time:
Car object Engine object
+----------------+ +------------------------+
| brand = "Tata" | <----------------| this$0 (hidden field) |
+----------------+ reference +------------------------+
The field is usually named this$0, and the constructor secretly takes the outer object as an extra argument. That's how brand inside Engine really means this$0.brand. (Older Java versions also generated hidden helper methods so the inner class could reach private fields. Java 11 and newer handle this directly in the JVM.)
This hidden link has a side effect worth remembering: an inner object keeps its outer object alive. As long as the engine is reachable, the car can't be garbage collected, even if you no longer use the car directly.
Name clashes and Outer.this
What if the inner and outer classes both have a field with the same name? You can pick exactly the one you want:
class Outer {
int x = 10;
class Inner {
int x = 20;
void show() {
int x = 30;
System.out.println(x);
System.out.println(this.x);
System.out.println(Outer.this.x);
}
}
}
new Outer().new Inner().show();
Output:
30
20
10
-
xalone is the closest one (the local variable). -
this.xis the inner object's field. -
Outer.this.xis the outer object's field. That's the hidden link, written out.
A small history note: older versions of Java didn't allow static members inside an inner class. Java 16 relaxed that, so newer code can have them.
Static nested class
Now add the word static. A static nested class is a nested class that is not tied to any outer object. It's basically an ordinary class that happens to live inside another class for organization.
class Library {
private static int totalBooks = 0;
static class Book {
private final String title;
Book(String title) {
this.title = title;
totalBooks++;
}
String getTitle() {
return title;
}
}
static int getTotalBooks() {
return totalBooks;
}
}
Library.Book b1 = new Library.Book("Java Basics");
Library.Book b2 = new Library.Book("Clean Code");
System.out.println(b1.getTitle());
System.out.println(Library.getTotalBooks());
Output:
Java Basics
2
Notice the differences from the inner class:
- We create it with
new Library.Book(...). NoLibraryobject is needed. - It can use the outer class's static members, even the private ones (
totalBooks). - It cannot use the outer class's instance members directly, because there's no outer object to read them from.
A common confusion: you can't make a top-level class static. The keyword only makes sense for a class nested inside another one. And static here doesn't mean that all its members are static. It only means "no hidden link to an outer object."
Behind the scenes
A static nested class is compiled to its own file too (Library$Book.class), but it has no this$0 field. It's lighter, and it can't accidentally keep an outer object alive.
Member inner class Static nested class
Engine object Book object
+------------------------+ +--------------------+
| this$0 -> Car object | | title |
| (hidden outer link) | | (no outer link) |
+------------------------+ +--------------------+
You've already used static nested types without noticing. Map.Entry (the type of each item when you loop over a map) is a nested interface inside Map. And nested interfaces and enums are always static automatically.
Local inner class
A local inner class is declared inside a method. It exists only within that method, like a local variable.
public class Demo {
public static void main(String[] args) {
int base = 100;
class Adder {
int add(int n) {
return base + n;
}
}
System.out.println(new Adder().add(5));
}
}
Output:
105
It's rarely needed, but it shows one important rule. A local (or anonymous) class can use local variables from the method only if they are final or effectively final, which means their value never changes after being set.
Why? Because the compiler copies the value into the class (it creates a hidden field like val$base). If the variable could change after the copy, the two would go out of sync, and Java prevents that confusion by banning the change.
Anonymous inner class
An anonymous inner class is a class with no name that you declare and create in a single expression. You use it when you need a one-time implementation of an interface or a one-time version of a class.
The syntax is new SomethingToExtendOrImplement() { ...body... }.
Implementing an interface
public class Main {
interface Greeting {
void sayHello(String name);
}
public static void main(String[] args) {
Greeting g = new Greeting() {
@Override
public void sayHello(String name) {
System.out.println("Hello, " + name);
}
};
g.sayHello("Asha");
System.out.println(g.getClass().getName());
}
}
Output:
Hello, Asha
Main$1
Read it slowly. new Greeting() { ... } doesn't create a Greeting (you can't, it's an interface). It creates an object of a new unnamed class that implements Greeting, and the braces hold that class's body.
The last line reveals the secret: the compiler gave the nameless class a name anyway, Main$1. It numbers anonymous classes 1, 2, 3 and so on, and each one gets its own .class file.
Extending a class
You can also change the behavior of a normal class for just one object:
class Animal {
void sound() {
System.out.println("Some sound");
}
}
Animal cat = new Animal() {
@Override
void sound() {
System.out.println("Meow");
}
};
cat.sound();
Output:
Meow
No Cat class was ever written. If the parent's constructor takes arguments, you pass them inside the parentheses: new Animal("x") { ... }. An anonymous class can't have its own constructor, because it has no name to give one.
A real-life example
Before lambdas existed, this is how everyone sorted with a custom rule:
List<String> names = new ArrayList<>(List.of("Meera", "Jo", "Ravi"));
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return Integer.compare(a.length(), b.length());
}
});
System.out.println(names);
Output:
[Jo, Ravi, Meera]
The list is sorted by the length of each name. The same pattern was used for button click handlers, threads (new Runnable() { ... }) and many other callbacks.
Things to know
-
thisis the anonymous object, not the outer one. To reach the outer object, writeOuter.this. - Captured local variables must be effectively final. This won't compile:
int count = 0;
Runnable r = new Runnable() {
public void run() {
count++;
}
};
local variables referenced from an inner class must be final or effectively final
- An anonymous class isn't the same as an anonymous object. An anonymous object is
new Dog().bark()(a normal class, but no variable). An anonymous class is a class with no name. They can even appear together, but they're separate ideas.
Look at all that typing
Look back at the Greeting and Comparator examples. For one tiny piece of logic (a single method), we wrote a new, a type name, braces, @Override, a full method signature and more braces. The real idea was a single line: "compare the lengths."
When the interface has exactly one abstract method, this ceremony is pure noise. Java 8 introduced a much shorter way to write it, called a lambda expression. That's the topic of the next post, and it builds directly on what you just learned.
Which one should I use?
My default rule is this: make a nested class static unless it truly needs the outer object. Static is lighter, and it avoids the hidden link that can keep objects alive.
-
Static nested class: for a helper that belongs with the outer class but doesn't need an outer instance. Examples: a
Nodein a linked list, aBuilder, anEntry. -
Member inner class: when the helper needs the outer object's data. A classic example is an
Iteratorfor your own collection, which has to read the collection's fields. - Local inner class: only for a one-off helper inside a single method. It's uncommon.
- Anonymous inner class: for a one-time implementation of an interface or abstract class, especially when the body is a few lines. For interfaces with a single method, prefer a lambda.
- A normal top-level class: when many classes use it, when it's large or when you want to test it separately.
You can also control who sees a nested class. Unlike top-level classes, nested classes can be private, protected, default or public. A private static class Node tucked inside a collection is a very common way to hide a helper.
Common mistakes
-
Creating an inner class without an outer object.
new Car.Engine()fails with "an enclosing instance... is required." Usecar.new Engine(), or make the classstatic. -
Making every nested class non-static by habit. It adds a hidden outer reference you may not need, and it can keep big outer objects in memory for longer than you expect. Add
staticunless you need the outer object. - Changing a local variable inside a local or anonymous class. Captured variables must be effectively final. Use a field or a one-element array if you really need to change something.
-
Mixing up
this. Inside an inner or anonymous class,thisis the inner object. UseOuter.thisto reach the outer one. - Writing long logic in an anonymous class. It becomes hard to read and can't be reused. Give it a proper name, and for a single-method interface, use a lambda.
- Misunderstanding the word "static." A top-level class can't be static, and a static nested class doesn't make its own members static. It only means "no outer object needed."
Quick recap
- A nested class is a class written inside another class. Static nested classes have the
statickeyword. Inner classes (strictly speaking) don't. - A member inner class is tied to an outer object through a hidden reference (
this$0), and it can use the outer's private members. Create it withouter.new Inner(). - A static nested class has no outer object, can use the outer's static members, and is created with
new Outer.Nested(). - A local inner class lives inside a method. Local and anonymous classes can only use effectively final local variables.
- An anonymous inner class is a nameless class created with
new Type() { ... }. The compiler names itOuter$1,Outer$2and so on. - Every nested class gets its own
.classfile, namedOuter$Inner.class. -
Default to
staticunless you need the outer object.
Try it yourself
Here's a small practice idea: build a simple MyStack class that stores items in a linked chain. Put a private static class Node inside it to hold each item. Then add an Iterator implemented as a member inner class that walks through the nodes, and use it in a for-each loop. You'll see why the iterator needs to be inner (it reads the stack's fields) and why the node can be static (it doesn't).
For a bonus experiment, compile any file with nested and anonymous classes, then list the output folder. Count the $ files and match each one to a class in your code.
If this helped, or if you spotted something I should fix, tell me in the comments. I'm still learning too.
Next in this series: functional interfaces and lambda expressions.
Top comments (0)