skip to content

When a static nested class is compiled, what .class file is produced and how is it named?

level: middleimportance: should knowfreq 45%

answer

  1. separate .class per nested class
  2. Outer$Nested.class — dollar separator
  3. binary name uses $, canonical uses .
  4. Class.forName needs the $ form
  5. anonymous → Outer$1.class; nest metadata since Java 11

basics

~10 s

Each nested class compiles to its own separate .class file. The name joins the outer and nested names with a dollar sign: Outer$Nested.class, alongside Outer.class.

solid answer

~40 s

Java has no real concept of a class physically inside another at the bytecode/runtime level — nesting is a source-language feature. So the compiler flattens it: a static nested class `Nested` inside `Outer` is emitted as its own class file named `Outer$Nested.class`, sitting next to `Outer.class` in the same package directory. The `$` is the separator the compiler uses between the enclosing name and the nested name. Anonymous classes get numeric names like `Outer$1.class`, and the JVM records the original nesting relationship in metadata (the `InnerClasses` attribute, plus `NestHost`/`NestMembers` since Java 11) so reflection and access control still work. A practical consequence: the fully qualified *binary* name uses `$` (`com.example.Outer$Nested`), which is what you must pass to `Class.forName`, even though source code and the canonical name use a dot (`Outer.Nested`).

code

java · 11 lines
java
// Outer.java compiles to Outer.class AND Outer$Nested.class
public class Outer {
    static class Nested { }
}

class Loader {
    static Class<?> load() throws ClassNotFoundException {
        // Must use the binary name with '$', not a dot
        return Class.forName("Outer$Nested");
    }
}

go deeper

for a junior

Knows the compiler creates a separate file named with a dollar sign, e.g. Outer$Nested.class.

for a middle

Explains binary vs. canonical name and that Class.forName/reflection needs the $ form; recognizes Outer$1 for anonymous classes.

for a senior

Understands the InnerClasses/NestHost metadata that preserves nesting for access control and reflection, and the Java 11 nestmate change.

for a principal

Reasons about implications for tooling, classloading, obfuscation, serialization compatibility, and bytecode-level access across a build/runtime pipeline.

## Nesting is a source-level illusion In Java *source code* a class can sit inside another. But the **JVM** (the runtime) and the **class file format** (the compiled `.class` binary) have no notion of one class living inside another. Every class is a flat, independent entity at runtime. So when `javac` (the compiler) sees a nested class, it must **flatten** the structure into separate class files. ## What gets produced Given: ```java // file: Outer.java public class Outer { static class Nested { } } ``` Compiling produces **two** files in the same package/output directory: ``` Outer.class Outer$Nested.class ``` The `$` (dollar sign) is the **separator** the compiler inserts between the **enclosing** class name and the **nested** class name. This is true for both static nested classes and non-static inner classes — the file-naming scheme does not distinguish them by name (the distinction lives in metadata). ## The binary name vs. the canonical name This flattening creates two ways to spell the same type: - **Canonical name** (source style, dotted): `com.example.Outer.Nested` - **Binary name** (runtime/class-file style, dollar): `com.example.Outer$Nested` The binary name is what you must hand to reflection: ```java Class<?> c = Class.forName("com.example.Outer$Nested"); // works // Class.forName("com.example.Outer.Nested") // throws ClassNotFoundException ``` ## Other nested kinds - **Anonymous classes** have no source name, so the compiler numbers them: `Outer$1.class`, `Outer$2.class`, … - **Local classes** (named classes inside a method) may also get a number prefix to disambiguate: `Outer$1Local.class`. - **Deeper nesting** chains the separators: a class `B` inside `A` inside `Outer` → `Outer$A$B.class`. ## How nesting is preserved at runtime Even though the files are flattened, the JVM still needs to know they were nested — for things like access to each other's private members and for reflection (`getDeclaringClass()`, `getEnclosingClass()`). The compiler records this in class-file metadata: - The **`InnerClasses` attribute** captures the nesting relationship and the source-declared modifiers. - Since **Java 11**, the **`NestHost` / `NestMembers`** attributes define a *nest* — the group of classes that originated from the same top-level type — so that nestmates can directly access each other's private members **without** the synthetic bridge `access$000` methods the compiler used to generate. ## Defined terms - **`javac`**: the Java source-to-bytecode compiler. - **`.class` file / class file**: the compiled binary for exactly one class. - **Binary name**: the runtime identifier of a type, using `$` for nesting and `.` for packages. - **Nest (Java 11+)**: the set of classes compiled from one top-level type that are permitted private access to one another. ## Why you might care - Loading a nested type by name (reflection, frameworks, deserialization) requires the `$` form. - Tooling, stack traces, and jar listings show the `$` names — recognizing them tells you the class was nested. - A jar that seems to be 'missing' `Outer.Nested` actually contains `Outer$Nested.class`.

  • Why does Class.forName("Outer.Nested") fail while "Outer$Nested" works?
    Class.forName takes the binary name, which uses '$' to separate enclosing and nested names and reserves '.' for package separators. 'Outer.Nested' would be interpreted as class 'Nested' in package 'Outer', which doesn't exist.
  • What changed about nested-class private access in Java 11?
    Java 11 introduced nestmates (NestHost/NestMembers attributes). Classes from the same top-level type form a 'nest' and can access each other's private members directly, eliminating the synthetic bridge methods (like access$000) the compiler previously generated.

saying these in an interview costs you the question

  • Thinking a nested class is stored inside the outer .class file — it gets its own file.
  • Using the dotted canonical name with Class.forName instead of the $ binary name.
  • Assuming the file name distinguishes static nested from inner classes — it doesn't; metadata does.
  • Confusing the dollar separator with something you typed — it's compiler-generated.

context