Difference between a static nested class and an inner (no… — Cracked Java
MidTheory

Difference between a static nested class and an inner (non-static) class.

A static nested class is an ordinary class that just lives inside another class's namespace; a non-static inner class holds a hidden reference to its enclosing instance. That one hidden reference (this$0) is the source of every meaningful difference: construction syntax, memory footprint, serialization behavior, and the classic enclosing-instance memory leak.

The hidden field

class Outer {
    int x;
    class Inner { void use() { System.out.println(x); } }
    static class Nested { /* no enclosing reference */ }
}

After compilation:

Outer$Inner.class:
  - field synthetic Outer this$0
  - constructor takes Outer as its first argument
  - body of use() reads this$0.x

Outer$Nested.class:
  - no synthetic field
  - constructor takes only its declared args

Inner is structurally larger by one reference per instance, and every method that touches an Outer member compiles to an extra getfield this$0 indirection. Nested looks like any other class.

Construction syntax

Outer o = new Outer();
Outer.Nested n = new Outer.Nested();   // no Outer needed
Outer.Inner  i = o.new Inner();        // weird — must qualify with an outer

You cannot construct an Inner without an enclosing Outer instance. The compiler will refuse new Outer.Inner() unless you're in an instance context of Outer.

Memory implications

The hidden reference keeps the enclosing instance reachable for the entire lifetime of every inner instance.

class HugeOuter {
    byte[] cache = new byte[1024 * 1024 * 100];  // 100 MB

    Runnable makeTask() {
        return new Runnable() {                  // anonymous inner class
            @Override public void run() { /* doesn't use cache */ }
        };
    }
}

Runnable r = new HugeOuter().makeTask();
// HugeOuter is unreachable... except r holds an anonymous Inner
// whose this$0 points to it. The 100 MB stays alive.

A static nested class wouldn't capture, so the 100 MB would be eligible for GC. This is the foundation of the leak question.

Serialization

Inner carries Outer along when serialized:

new Outer().new Inner();          // ser includes the Outer instance

That's a footgun — your serialized inner class instances drag in their entire enclosing context, including fields you never intended to persist. Static nested classes serialize like ordinary classes.

Statics inside

Pre-Java-16, inner classes couldn't declare static members (the language said "an inner class can't own state independent of its enclosing instance"). Java 16 lifted this for records and for general nested classes. Static nested classes were always free to declare statics.

When to choose which

NeedUse
Helper that doesn't reference enclosing stateStatic nested
Iterator/view tightly bound to enclosing stateInner
Anything where you might keep references long-termStatic nested (avoid leaks)
Anything serializableStatic nested
Anonymous one-line callbackLambda if possible, anonymous otherwise (but be aware of the this$0 capture)

The Effective Java rule

"If you declare a member class that does not require access to an enclosing instance, always put the static modifier in its declaration." — Item 24.

The advice is rarely wrong. The default should be static; promote to inner only when you genuinely need the enclosing instance.

Mark your status