Skip to content

Java Inner Classes

Updated 10 min read Aimerscrew

A Java inner class is a class written inside another class. Until now, every class, record and enum in this chapter sat side by side at the top of a file. Sometimes, though, a class only makes sense as a part of another one, like a cursor that moves through one particular playlist. Java gives you four ways to nest classes. In this lesson, you'll learn static nested classes, inner classes, local classes and anonymous classes, and you'll get a simple rule for choosing between them.

🌍 Real-life example

Think of the parking spots in a multi-story garage. Spot 12 doesn't mean much on its own: which garage? A spot belongs to one garage and knows whether that garage is open. A library card is different: it's issued by the library system, but you can carry it around without needing a particular building. Java's inner classes and static nested classes mirror exactly this difference.

What is a nested class in Java?

A nested class is any class declared inside another class, which is called the outer class (or enclosing class). There are four kinds:

KindWhere it's declaredTied to an outer object?
Static nested classInside a class, with staticNo
Inner classInside a class, without staticYes
Local classInside a methodYes, if the method isn't static
Anonymous classInside an expression, with no nameYes, if created in a non-static method

Strictly speaking, Java calls the last three "inner classes" and the first one a "static nested class", but most people use "inner class" loosely for all of them.

Static nested classes

A static nested class is a normal class that simply lives inside another class's name, like a folder. It does not belong to any object of the outer class. You create it with new Outer.Nested(...):

public class LibraryDesk {
    public static void main(String[] args) {
        Library.Card card = new Library.Card("Ana Costa", 1042);   // no Library object needed
        System.out.println(card.describe());
        System.out.println("Max loans: " + Library.Card.MAX_LOANS);
    }
}

class Library {
    private String branch = "Central";

    static class Card {                      // static nested class
        static final int MAX_LOANS = 5;
        private String holder;
        private int number;

        Card(String holder, int number) {
            this.holder = holder;
            this.number = number;
        }

        String describe() {
            return "Card #" + number + " belongs to " + holder;
            // 'branch' is NOT available here: a Card has no Library object
        }
    }

    String getBranch() {
        return branch;
    }
}
Card #1042 belongs to Ana Costa
Max loans: 5

Because a Card has no Library object attached, it can't read the instance field branch. Trying gives the same error you saw in the static lesson: non-static variable branch cannot be referenced from a static context. Static nested classes are great for helper types that belong with their outer class by name, such as Map.Entry in Java's own library.

Inner classes (non-static)

An inner class is declared without static. Each inner object is created for one specific outer object and keeps a hidden reference to it. That's why it can use the outer object's fields and methods directly, even private ones:

public class MusicApp {
    public static void main(String[] args) {
        Playlist roadTrip = new Playlist("Road Trip", new String[] { "Sunrise Drive", "Open Highway", "Coastline" });

        Playlist.Cursor cursor = roadTrip.cursor();
        System.out.println(cursor.current());
        cursor.next();
        System.out.println(cursor.current());
        cursor.next();
        cursor.next();                      // wraps around to the first song
        System.out.println(cursor.current());
    }
}

class Playlist {
    private String name;
    private String[] songs;

    Playlist(String name, String[] songs) {
        this.name = name;
        this.songs = songs;
    }

    Cursor cursor() {
        return new Cursor();                 // this Cursor belongs to this Playlist
    }

    class Cursor {                           // inner class (not static)
        private int index = 0;

        void next() {
            index = (index + 1) % songs.length;    // reads the outer object's field
        }

        String current() {
            return name + " > " + songs[index];   // name and songs come from Playlist
        }
    }
}
Road Trip > Sunrise Drive
Road Trip > Open Highway
Road Trip > Sunrise Drive
Animated diagram comparing a static nested Library.Card object, which stands alone with no link to any Library object, with an inner Playlist.Cursor object, which holds a hidden reference called Playlist.this pointing to its Playlist object; when current() runs, it follows that link to read name and songs
An inner object carries a hidden link to its outer object; a static nested object does not.

Inside Cursor, the names songs and name really mean Playlist.this.songs and Playlist.this.name. Playlist.this is the hidden reference to the outer object. You rarely need to write it, except when an inner field has the same name as an outer one.

Since an inner object needs an outer object, new Playlist.Cursor() from main fails with an enclosing instance that contains Playlist.Cursor is required. The usual approach is a method in the outer class, like cursor(), that creates it. (The rarely used direct form is roadTrip.new Cursor().)

💡 Tip

If your nested class doesn't use any instance fields or methods of the outer class, make it static. It's simpler, and it doesn't keep the outer object alive in memory.

Local classes

A local class is declared inside a method, just like a local variable. It exists only within that method, so no other code can use it. It can read the method's parameters and local variables, as long as they are never changed:

public class ReceiptPrinter {
    public static void main(String[] args) {
        printReceipt("EUR", new String[] { "Croissant", "Cappuccino" }, new double[] { 2.40, 3.10 });
    }

    static void printReceipt(String currency, String[] items, double[] prices) {

        class Line {                         // local class: exists only in this method
            private String item;
            private double price;

            Line(String item, double price) {
                this.item = item;
                this.price = price;
            }

            String format() {
                return String.format("%-12s %s %.2f", item, currency, price);   // uses a local variable
            }
        }

        double total = 0;
        for (int i = 0; i < items.length; i++) {
            Line line = new Line(items[i], prices[i]);
            System.out.println(line.format());
            total += prices[i];
        }
        System.out.println(new Line("TOTAL", total).format());
    }
}
Croissant    EUR 2.40
Cappuccino   EUR 3.10
TOTAL        EUR 5.50

The Line class uses currency, a parameter of the method. That works because currency is effectively final: it's never reassigned after it gets its value. If you added currency = "USD"; anywhere in the method, the compiler would complain: local variables referenced from an inner class must be final or effectively final.

Local classes are fairly rare in practice. They're handy when a small helper type is needed in one long method only.

Anonymous classes

An anonymous class is a class with no name that you declare and instantiate in one expression. You write new, an interface or class name, and then a class body in curly braces. It's perfect for a one-off implementation of an interface:

public class Checkout {
    public static void main(String[] args) {
        DiscountRule blackFriday = new DiscountRule() {     // anonymous class
            @Override
            public double apply(double price) {
                return price * 0.70;
            }

            @Override
            public String label() {
                return "Black Friday (30% off)";
            }
        };

        DiscountRule loyalty = new DiscountRule() {         // another one
            @Override
            public double apply(double price) {
                return Math.max(0, price - 5.00);
            }

            @Override
            public String label() {
                return "Loyalty ($5 off)";
            }
        };

        double price = 40.00;
        for (DiscountRule rule : new DiscountRule[] { blackFriday, loyalty }) {
            System.out.printf("%s: $%.2f%n", rule.label(), rule.apply(price));
        }
    }
}

interface DiscountRule {
    double apply(double price);

    String label();
}
Black Friday (30% off): $28.00
Loyalty ($5 off): $35.00

Read new DiscountRule() { ... } as "create an object of a brand-new, unnamed class that implements DiscountRule, with this body". Notice the semicolon after the closing brace: the whole thing is one statement. We never had to invent names like BlackFridayRule for classes used only once.

Anonymous classes with Comparator

A classic use is telling Arrays.sort() (from the Arrays class) how to order things. Comparator is a built-in interface whose compare(a, b) method returns a negative number if a should come first, a positive number if b should, and 0 if they tie:

import java.util.Arrays;
import java.util.Comparator;

public class SortSongs {
    public static void main(String[] args) {
        String[] songs = { "Coastline", "Open Highway", "Rain", "Sunrise Drive" };

        Arrays.sort(songs, new Comparator<String>() {
            @Override
            public int compare(String a, String b) {
                return a.length() - b.length();      // shorter titles first
            }
        });

        System.out.println(Arrays.toString(songs));
    }
}
[Rain, Coastline, Open Highway, Sunrise Drive]

The <String> part says this comparator compares strings; you'll learn about it in Java generics.

📝 Note

An anonymous class that implements an interface with just one abstract method can usually be replaced by a much shorter lambda expression, such as (a, b) -> a.length() - b.length(). You'll learn lambdas in Java lambda expressions. Anonymous classes are still needed when the interface has several methods, like DiscountRule above.

Which kind of nested class should you use?

Use a...WhenExample
Top-level classThe class is useful on its ownBook, Customer
Static nested classA helper that belongs with the outer class by name but needs no outer objectLibrary.Card, Map.Entry
Inner classEach object must belong to one outer object and use its dataPlaylist.Cursor, Garage.Spot
Local classA named helper needed inside one method onlyLine in printReceipt()
Anonymous classA one-off implementation used in a single placeA Comparator, a DiscountRule

When in doubt, prefer a static nested class over an inner class, and a top-level class over both.

Common mistakes with inner classes

1. Creating an inner object without an outer object

new Playlist.Cursor() fails because a cursor must belong to a playlist. Create it through an outer object, for example with a method like roadTrip.cursor(), or make the class static if it doesn't need the outer data.

2. Using outer instance fields in a static nested class

A static nested class has no outer object, so outer instance fields aren't available. Pass the data in through the constructor, or make it an inner class if it truly belongs to one object.

3. Changing a variable used by a local or anonymous class

Local variables captured by these classes must be effectively final. If you need a changing value, store it in a field instead.

4. Forgetting the semicolon after an anonymous class

DiscountRule r = new DiscountRule() { ... } is a variable declaration, so it ends with };. Leaving out the semicolon is a very common compile error.

Practice exercise

🏋️ Try it yourself

Create a Garage class with a private name and a private boolean open that starts as true, plus a close() method. Add an inner class Spot with a number and a method label() that returns something like Downtown-12 (open), using the outer garage's fields. Add a method spot(int number) that creates a spot. Make spot 12 in "Downtown" and spot 7 in "Airport", close Downtown, then print both labels.

Show solution
public class ParkingGarage {
    public static void main(String[] args) {
        Garage downtown = new Garage("Downtown");
        Garage airport = new Garage("Airport");

        Garage.Spot a = downtown.spot(12);
        Garage.Spot b = airport.spot(7);
        downtown.close();

        System.out.println(a.label());
        System.out.println(b.label());
    }
}

class Garage {
    private String name;
    private boolean open = true;

    Garage(String name) {
        this.name = name;
    }

    Spot spot(int number) {
        return new Spot(number);
    }

    void close() {
        open = false;
    }

    class Spot {
        private int number;

        Spot(int number) {
            this.number = number;
        }

        String label() {
            return name + "-" + number + (open ? " (open)" : " (closed)");
        }
    }
}
Downtown-12 (closed)
Airport-7 (open)

Spot a was created while Downtown was open, but it prints "closed": it reads its garage's current state through the hidden link. Spot b belongs to a different garage, so it isn't affected.

What's next?

Congratulations on finishing the chapter on inheritance and interfaces! You can now design class families, contracts, enums, records and nested helpers. In the next chapter, you'll learn what happens when things go wrong at run time, and how to handle it gracefully. Start with Java exceptions.

Key takeaways

  • A nested class is declared inside another class; Java has static nested, inner, local and anonymous classes.
  • A static nested class needs no outer object and is created with new Outer.Nested(); it cannot use outer instance fields.
  • An inner (non-static) class object belongs to one outer object and can use its fields, even private ones, through the hidden Outer.this reference.
  • A local class is declared inside a method and can use that method's effectively final variables.
  • An anonymous class declares and creates a one-off, unnamed class in a single expression, often to implement an interface such as Comparator.
  • Prefer static nested classes over inner classes unless the nested object really needs its outer object.
  • Single-method anonymous classes can usually be replaced with lambda expressions.

Frequently asked questions

What is an inner class in Java?

An inner class is a non-static class declared inside another class. Each inner object is linked to an object of the outer class, so it can access that object's fields and methods, including private ones.

What is the difference between a static nested class and an inner class?

A static nested class is not tied to any outer object and cannot use the outer class's instance fields. An inner class is tied to one outer object, keeps a hidden reference to it, and can use its instance fields.

What is an anonymous class in Java?

An anonymous class is a class without a name that is declared and instantiated in one expression, such as new Comparator<String>() { ... }. It is used for one-off implementations of an interface or subclasses of a class.

Can an inner class access private members of the outer class?

Yes. Inner classes, and static nested classes too, can access private members of their outer class. A static nested class still needs an outer object to reach instance members.

Should I use an anonymous class or a lambda?

If the interface has exactly one abstract method, a lambda is shorter and clearer. Use an anonymous class when the interface has several abstract methods, when you extend a class, or when you need fields inside the object.