Nested Classes

Su Suriya Ravichandran Updated 14 Nov 2026
8 min read ·Lesson 59 of 133

A nested class is a class declared inside another class. Java allows this so that a type that exists only to serve one other class can live inside it: logically grouped, optionally hidden from the rest of the program, and with access to the enclosing class's private members. Nested classes are common in real code: builders, iterators, map entries, event handlers, and small data types are often nested.

The Kinds of Nested Classes

Nested classes
├── Static nested class          static class Inner { }        (no link to an outer object)
└── Inner classes (non-static)
    ├── Member inner class       class Inner { }               (tied to an outer object)
    ├── Local class              declared inside a method
    └── Anonymous class          new Interface() { ... }       (no name, one expression)
KindDeclaredNeeds an outer instance?Typical use
Static nested classAs a class member, with staticNoBuilders, helper types, small data types
Member inner classAs a class member, without staticYesIterators and views over the outer object's data
Local classInside a method bodyYes, if in an instance methodA helper needed only within one method
Anonymous classInline, in an expressionYes, if in an instance methodOne-off implementations of interfaces

Nested interfaces, enums, and records are always implicitly static.

This lesson covers static nested classes and the general rules. Inner classes and anonymous classes have their own lessons.

Static Nested Classes

public class Order {
    private final String id;
    private final List<Line> lines = new ArrayList<>();

    public Order(String id) { this.id = id; }

    public void add(String product, int qty, double price) {
        lines.add(new Line(product, qty, price));
    }

    public double total() {
        double sum = 0;
        for (Line l : lines) sum += l.subtotal();
        return sum;
    }

    public static class Line {
        private final String product;
        private final int qty;
        private final double price;

        public Line(String product, int qty, double price) {
            this.product = product;
            this.qty = qty;
            this.price = price;
        }

        public double subtotal() { return qty * price; }
    }
}
Order order = new Order("ORD-1001");
order.add("Pen", 10, 12.5);
order.add("Notebook", 2, 80);
System.out.println(order.total());              // 285.0

Order.Line line = new Order.Line("Bag", 1, 999);  // created without any Order

A static nested class behaves like an ordinary top-level class that happens to be declared inside another:

  • Its full name is Outer.Nested (here Order.Line).
  • It is created with new Outer.Nested(...), with no outer object required.
  • It has no reference to an instance of the outer class, so it cannot use the outer class's instance fields directly.
  • It can access the outer class's private static members, and the private instance members of any outer object it is given.

Compiled, it becomes a separate file: Order$Line.class.

Why nest it at all?

  • Grouping. Order.Line says clearly that a line belongs to an order. A top-level class named Line would be ambiguous.
  • Encapsulation. A private static nested class is invisible outside the enclosing class, keeping helper types out of the package's API:
public class Cache {
    private final Map<String, Entry> entries = new HashMap<>();

    private static class Entry {           // implementation detail
        final String value;
        final long expiresAt;
        Entry(String value, long expiresAt) {
            this.value = value;
            this.expiresAt = expiresAt;
        }
    }

    public void put(String key, String value, long ttlMillis) {
        entries.put(key, new Entry(value, System.currentTimeMillis() + ttlMillis));
    }

    public String get(String key) {
        Entry e = entries.get(key);
        if (e == null || e.expiresAt < System.currentTimeMillis()) {
            return null;
        }
        return e.value;
    }
}

Callers see only put and get. Entry can be redesigned without affecting anyone.

  • Shared private access. The outer and nested classes can access each other's private members, which suits tightly coupled pairs such as a class and its builder.

Access Between Outer and Nested Classes

A nested class is a member of its enclosing class, so the usual privacy boundary does not separate them:

public class Account {
    private double balance;                 // private to Account
    private static int created = 0;

    static class Auditor {
        void check(Account a) {
            System.out.println(a.balance);  // allowed: nested class reads outer's private field
            System.out.println(created);    // allowed: private static field of the outer class
        }
    }

    void inspect(Auditor auditor) {
        auditor.check(this);
    }
}

The reverse also holds: the outer class can access private members of its nested classes. This mutual access is intentional, since both are parts of one unit of code.

The Builder Pattern with a Static Nested Class

The most common use of a static nested class in modern Java is a builder:

public class HttpConfig {
    private final String baseUrl;
    private final int timeoutSeconds;
    private final int retries;
    private final boolean followRedirects;

    private HttpConfig(Builder b) {              // only the builder can construct
        this.baseUrl = b.baseUrl;
        this.timeoutSeconds = b.timeoutSeconds;
        this.retries = b.retries;
        this.followRedirects = b.followRedirects;
    }

    public static Builder builder(String baseUrl) {
        return new Builder(baseUrl);
    }

    public static class Builder {
        private final String baseUrl;
        private int timeoutSeconds = 30;          // defaults
        private int retries = 0;
        private boolean followRedirects = true;

        private Builder(String baseUrl) { this.baseUrl = baseUrl; }

        public Builder timeout(int seconds) { this.timeoutSeconds = seconds; return this; }
        public Builder retries(int n)       { this.retries = n; return this; }
        public Builder noRedirects()        { this.followRedirects = false; return this; }

        public HttpConfig build() {
            if (timeoutSeconds <= 0) throw new IllegalStateException("timeout must be positive");
            return new HttpConfig(this);
        }
    }

    @Override
    public String toString() {
        return baseUrl + " timeout=" + timeoutSeconds + "s retries=" + retries
                + " redirects=" + followRedirects;
    }
}
HttpConfig cfg = HttpConfig.builder("https://api.example.com")
        .timeout(10)
        .retries(3)
        .build();

System.out.println(cfg);
// https://api.example.com timeout=10s retries=3 redirects=true

HttpConfig's constructor is private, yet Builder can call it, because they are nested in the same class. Likewise, the constructor reads the builder's private fields directly. The builder must be static: it is created before any HttpConfig exists, so it cannot depend on an outer instance.

Static Versus Inner: The Key Difference

A non-static nested (inner) class carries a hidden reference to the outer object that created it:

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

    class Cursor {                         // inner: tied to one Playlist
        private int position = 0;
        String next() {
            return songs.get(position++ % songs.size());   // reads the outer object's songs
        }
    }
}

Playlist p = new Playlist();
Playlist.Cursor c = p.new Cursor();        // requires an outer instance
Static nestedInner (non-static)
Created withnew Outer.Nested()outerObject.new Inner()
Accesses outer instance fieldsOnly through an explicit referenceDirectly
Holds a reference to the outer objectNoYes (hidden)
MemoryIndependentKeeps the outer object alive while it exists

The hidden reference has a cost: an inner object prevents its outer object from being garbage collected, which can cause memory leaks when the inner object is long-lived (stored in a cache, registered as a listener, or handed to another thread).

Rule of thumb: declare nested classes static unless they genuinely need access to an outer instance. Inner classes are covered in the next lesson.

Nested Records, Enums, and Interfaces

These are implicitly static, and they are a convenient way to define small types close to where they are used:

public class Weather {
    public enum Condition { SUNNY, CLOUDY, RAIN, STORM }

    public record Reading(String city, double tempC, Condition condition) { }

    public interface Alert {
        boolean triggers(Reading r);
    }
}
var r = new Weather.Reading("Chennai", 34.5, Weather.Condition.SUNNY);
Weather.Alert heat = reading -> reading.tempC() > 33;
System.out.println(heat.triggers(r));    // true

Accessing Nested Types from Outside

A public nested type is referred to through its outer class, or imported directly:

import com.example.shop.Order;
Order.Line line = new Order.Line("Pen", 1, 10);

// or
import com.example.shop.Order.Line;
Line line = new Line("Pen", 1, 10);

The JDK uses this convention widely: Map.Entry, Character.UnicodeBlock, Thread.State, AbstractMap.SimpleEntry.

Practical Example: Leaderboard

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class Leaderboard {
    private final String game;
    private final List<Entry> entries = new ArrayList<>();

    public Leaderboard(String game) { this.game = game; }

    public void submit(String player, int score) {
        for (Entry e : entries) {
            if (e.player.equals(player)) {
                e.best = Math.max(e.best, score);   // outer class reads/writes nested private fields
                return;
            }
        }
        entries.add(new Entry(player, score));
    }

    public List<String> top(int n) {
        List<Entry> sorted = new ArrayList<>(entries);
        sorted.sort(Entry.BY_SCORE_DESC);
        List<String> result = new ArrayList<>();
        for (int i = 0; i < Math.min(n, sorted.size()); i++) {
            result.add((i + 1) + ". " + sorted.get(i));
        }
        return result;
    }

    private static class Entry {
        static final Comparator<Entry> BY_SCORE_DESC =
                Comparator.comparingInt((Entry e) -> e.best).reversed()
                          .thenComparing(e -> e.player);

        private final String player;
        private int best;

        Entry(String player, int best) {
            this.player = player;
            this.best = best;
        }

        @Override
        public String toString() { return player + " (" + best + ")"; }
    }

    public static void main(String[] args) {
        Leaderboard board = new Leaderboard("Typing Race");
        board.submit("ada", 82);
        board.submit("linus", 95);
        board.submit("grace", 88);
        board.submit("ada", 97);          // improves ada's best
        board.submit("linus", 90);        // lower than best; ignored

        System.out.println(board.game);
        board.top(3).forEach(System.out::println);
    }
}

Output:

Typing Race
1. ada (97)
2. linus (95)
3. grace (88)

Entry is a private static nested class: an implementation detail of Leaderboard that the rest of the program never sees, so its fields can be accessed directly by the outer class without getters. It is static because an entry does not need a reference back to the leaderboard. The comparator lives inside Entry, next to the fields it compares. If the leaderboard later switches to a Map or a database, only code inside this one file changes.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about Nested Classes and get answers drawn from it.

Signed-in readers only.