Inner Classes and Exceptions ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-09-25

Inner Classes in Java: Types and Examples

Write Street street = new Street(); inside the static main method of class Town, where Street is an inner class, and the compiler stops with non-static variable this cannot be referenced from a static context. The class name is fine. The problem is that every inner class object needs an enclosing Town object, and a static method does not have one.

An inner class in Java is a non-static class declared inside another class, a "class within a class". Each instance of an inner class is tied to an instance of the outer (enclosing) class and can access all of its members, including private fields and methods, as if the inner class were part of the outer class. Inner classes help group closely related code, improve encapsulation and implement interfaces right where they are needed.

Java has four kinds of nested classes (classes declared inside another class):

  1. Member inner class (regular inner class)
  2. Local class (method-local inner class)
  3. Anonymous inner class
  4. Static nested class (often called a "static inner class")

The first three are inner classes. According to the Java Language Specification, §8.1.3, a static nested class is not an inner class, because it is not bound to an instance of the outer class.

1. Member (Regular) Inner Class

A member inner class (also called a regular inner class) is declared directly in the body of the outer class, at the same level as fields and methods, without the static modifier.

  • To create an instance of the inner class, an instance of the outer class must exist.
  • The inner and outer classes can access each other's members, even private ones.

Java inner class example

The inner class Street is declared inside the outer class Town:

public class Town {
    private String postCode = "33333";

    public class Street {
        private int house;

        public void printAddress() {
            System.out.println("Town is " + Town.this);
            System.out.println("PostCode is " + postCode); // private field of Town
            System.out.println("Street is " + this);
            System.out.println("House is " + house);
        }
    }

    public void createStreet() {
        Street street = new Street(); // implicitly bound to this Town
        street.house = 78;            // outer class reads a private field of the inner class
        street.printAddress();
    }

    public static void main(String[] args) {
        Town town = new Town();
        town.createStreet();

        Street street1 = town.new Street();
        Street street2 = new Town().new Street();
        street1.printAddress();
        street2.printAddress();
    }
}

How to instantiate an inner class

  • Inside an instance (non-static) method of the outer class, you create an inner class object like any other object. The current this becomes the enclosing instance: Street street = new Street();
  • Inside a static method of the outer class, such as main, there is no this, so you must supply the enclosing instance explicitly with the outer.new Inner() syntax: town.new Street() or new Town().new Street(). The simple name Street is still in scope.
  • Outside the outer class, the inner class type must be qualified with the outer class name:
Town town = new Town();
Town.Street street = town.new Street();

this and Outer.this

Inside the inner class, this refers to the inner class instance. To reach the enclosing outer instance, use the outer class name followed by .this, for example Town.this. This matters most when an inner class field shadows an outer field with the same name:

public class Town {
    private String name = "Springfield";

    class Street {
        private String name = "Main St";

        void print() {
            System.out.println(name);           // Main St
            System.out.println(this.name);      // Main St
            System.out.println(Town.this.name); // Springfield
        }
    }
}

Private inner class and access modifiers

A member inner class is a member of the outer class, just like fields and methods, so it accepts the same modifiers:

  • public, protected, private or default (package-private) access
  • final, which prevents subclassing
  • abstract
  • static, which turns it into a static nested class, which is no longer an inner class
  • strictfp (redundant since Java 17, see the note below)

A private inner class is visible only inside its outer class. It is the usual way to hide an implementation detail, for example an Iterator implementation behind a public interface:

import java.util.Iterator;
import java.util.NoSuchElementException;

public class Range implements Iterable<Integer> {
    private final int from;
    private final int to;

    public Range(int from, int to) {
        this.from = from;
        this.to = to;
    }

    @Override
    public Iterator<Integer> iterator() {
        return new RangeIterator(); // callers see only Iterator<Integer>
    }

    private class RangeIterator implements Iterator<Integer> {
        private int current = from; // reads the outer instance's private field

        @Override
        public boolean hasNext() {
            return current <= to;
        }

        @Override
        public Integer next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return current++;
        }
    }
}

Access works both ways: the inner class reads the outer's private from and to, and the outer class can read and write the private members of its inner class (as street.house = 78 does in the first example).

strictfp is obsolete

Since Java 17 (JEP 306), all floating-point arithmetic is strict by default. The strictfp modifier still compiles, but it changes nothing and the compiler warns that it is redundant. You will still see it in older certification questions.

2. Local Class (Method-Local Inner Class)

A local class, also called a method-local inner class, is declared inside a block of code, usually a method body. It is visible only inside that block. When it is declared in an instance method, it shares the same link with the outer object as a member inner class and can access all its members, including private ones.

Rules for local classes:

  • You can instantiate a local class only inside the same block and only after (below) its declaration.
  • A local class cannot be declared public, protected, private or static. Like a local variable, it has no access modifier.
  • A local class can be abstract or final.
  • It can use local variables and parameters of the enclosing method only if they are final or effectively final.

Local class example

public class Town {
    private String postCode = "33333";

    public void createAddress() {
        int houseNumber = 34; // effectively final: never reassigned

        class Street {
            public void printAddress() {
                System.out.println("PostCode is " + postCode);
                System.out.println("House Number is " + houseNumber);
            }
        }

        Street street = new Street();
        street.printAddress();
    }

    public static void main(String[] args) {
        new Town().createAddress();
    }
}

Local class in a static method

A local class declared in a static method has no enclosing instance. It can access only the static members of the outer class, plus the final or effectively final local variables of the method:

public class Town {
    private static String postCode = "33333";

    public static void createAddress() {
        final int houseNumber = 34;

        class Street {
            public void printAddress() {
                System.out.println("PostCode is " + postCode);         // static field: OK
                System.out.println("House Number is " + houseNumber); // final local: OK
            }
        }

        new Street().printAddress();
    }

    public static void main(String[] args) {
        Town.createAddress();
    }
}

Final and effectively final local variables

Up to Java 7, a local or anonymous class could read a local variable only if it was explicitly declared final. Since Java 8, the variable only has to be effectively final: it has no final keyword, but its value is never changed after initialization. That is why int houseNumber = 34; in the first local class example compiles without final.

As soon as the variable is reassigned anywhere, whether inside the local class or later in the method, it stops being effectively final:

public void createAddress() {
    int houseNumber = 34;

    class Street {
        public void printAddress() {
            houseNumber = 78; // compilation error
            System.out.println("House Number is " + houseNumber);
        }
    }

    new Street().printAddress();
}

The compiler reports: local variables referenced from an inner class must be final or effectively final.

Why does the rule exist? A local class object can outlive the method call. For example, it may be returned or passed to another thread. So the compiler does not keep a live reference to the variable. It copies the variable's value into a hidden field of the local class. If the variable could change afterwards, the copy and the original would silently disagree. Requiring (effectively) final makes the copy always correct. If you really need shared mutable state, use a field or a mutable holder such as AtomicInteger or a one-element array.

What changed in Java 16

Before Java 16, inner classes (member, local and anonymous) could not declare static members except static final constants. Code like static int counter; inside an inner class failed with illegal static declaration in inner class. Java 16 (JEP 395, delivered together with records) removed this restriction. Inner classes may now declare static fields and methods, and you can declare local record, enum and interface types inside a method. Exam questions based on Java 8/11 still expect the old "constants only" rule.

3. Anonymous Inner Class

An anonymous inner class in Java is a local class without a name, declared and instantiated in a single expression. It either extends a class or implements an interface, and it usually overrides one or two methods right where the object is needed.

Syntax of an anonymous inner class, where AnyType is a class or an interface:

AnyType ref = new AnyType(constructorArgs) {
    // fields, methods, instance initializer blocks
};

Key properties:

  • An anonymous class has no name, so it cannot declare a constructor. Arguments in new AnyType(...) go to the superclass constructor. Put your own initialization in an instance initializer block { ... }.
  • It extends exactly one class or implements exactly one interface, never both, and never several interfaces.
  • It can be declared in a method, in a field initializer, or directly as a method argument.
  • Like a local class, it can read only final or effectively final local variables.

Anonymous class that extends a class

Class Potato has one method, peel(). The anonymous subclass overrides peel() and also adds a new method fry(), which is allowed. However, the variable p has type Potato, and through a reference you can call only the methods declared in the reference type. So p.fry() does not compile:

class Potato {
    public void peel() {
        System.out.println("peeling potato");
    }
}

class Food {
    Potato p = new Potato() {
        public void fry() {
            System.out.println("frying potato anonymously");
        }

        @Override
        public void peel() {
            System.out.println("peeling potato anonymously");
            fry(); // OK: called inside the anonymous class
        }
    };

    public void prepare() {
        p.peel();
        p.fry(); // compilation error: Potato has no method fry()
    }
}

The var edge case

With a local variable declared via var (Java 10+), the inferred type is the anonymous class itself, so the extra method becomes callable: var p = new Potato() { void fry() { } }; p.fry(); compiles. With the explicit type Potato it does not. This only works for local variables, because fields cannot use var.

Anonymous class that implements an interface

When an anonymous class implements an interface, it must implement all of its abstract methods:

interface Cookable {
    void cook();
}

class Food {
    Cookable c = new Cookable() {
        @Override
        public void cook() {
            System.out.println("anonymous cookable implementer");
        }
    };
}

Note that new Cookable() { ... } does not create an instance of the interface. It creates an instance of a new unnamed class that implements Cookable.

Anonymous class as a method argument

An anonymous class can be written directly inside a method call:

class Food {
    public void prepare(Cookable c) {
        c.cook();
    }

    public static void main(String[] args) {
        Food food = new Food();
        food.prepare(new Cookable() {
            @Override
            public void cook() {
                System.out.println("Preparing something yummy.");
            }
        });
    }
}

A classic real-world case is a Comparator for sorting. The example also shows an instance initializer block, which does the job a constructor would normally do, and the diamond operator with an anonymous class (allowed since Java 9):

List<String> names = new ArrayList<>(List.of("Anna", "Jo", "Kirill"));

names.sort(new Comparator<>() {
    {
        System.out.println("Initializer block instead of a constructor");
    }

    @Override
    public int compare(String a, String b) {
        return Integer.compare(a.length(), b.length());
    }
});

System.out.println(names); // [Jo, Anna, Kirill]

To pass arguments to the superclass constructor, put them in the parentheses: new Thread("worker-1") { @Override public void run() { ... } }.

Anonymous class vs lambda expression

When the target is a functional interface, meaning an interface with exactly one abstract method such as Runnable or Comparator, modern code usually uses a lambda expression instead of an anonymous class:

// Anonymous class
Runnable task1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Working");
    }
};

// The same with a lambda
Runnable task2 = () -> System.out.println("Working");

You still need an anonymous class when:

  • you must extend a class (including an abstract class) rather than implement an interface;
  • the interface has more than one abstract method;
  • you need your own fields (state) or helper methods;
  • this must refer to the implementing object itself. In an anonymous class this is the anonymous object, while in a lambda it is the enclosing instance.

4. Static Nested Class (Static Inner Class)

A static nested class in Java is a nested class declared with the static modifier. It is often called a "static inner class", but by the JLS definition it is not an inner class at all: it has no link to an instance of the outer class.

  • It is created without an outer instance: new Outer.Nested().
  • It can directly access only the static members of the outer class, including private static ones. It can reach instance members only through an explicit reference to an outer object.
  • It behaves like a top-level class that is packaged inside another class for grouping and encapsulation. Well-known examples are Map.Entry and the Builder class in the Builder pattern.

Syntax of a static nested class:

class BigOuter {
    static class Nested {
    }
}

The next example shows how to instantiate a static nested class from outside its enclosing class and from inside it:

class Town {
    static class Street {
        void go() {
            System.out.println("Go to the Street");
        }
    }
}

class City {
    static class District {
        void go() {
            System.out.println("Go to the District");
        }
    }

    public static void main(String[] args) {
        Town.Street street = new Town.Street(); // no Town object needed; both names required
        street.go();

        District district = new District();     // inside City the simple name is enough
        district.go();
    }
}

Nested vs Inner Classes: JLS Terminology

The Java Language Specification uses precise terms, and exam questions rely on them:

  • Nested class is any class declared inside another class or interface. This is the umbrella term.
  • Inner class is a nested class that is not explicitly or implicitly static. Member, local and anonymous classes are inner classes.
  • Static nested class is a nested class declared static. It is not an inner class. Classes nested inside an interface, and nested enums and records, are implicitly static.

So "static inner class" is technically a contradiction in terms. In everyday speech it simply means a static nested class.

Comparison of the Four Kinds

Kind Where declared Needs an outer instance Access to outer members Access modifiers
Member inner class Class body, without static Yes All, including private public, protected, private, default
Local class Method or block Yes, if declared in an instance method All, plus effectively final local variables None allowed
Anonymous class A new expression Yes, if created in a non-static context All, plus effectively final local variables None allowed
Static nested class Class body, with static No Static members only (others via a reference) public, protected, private, default

How Nested Classes Are Compiled

The JVM has no special notion of "a class inside a class". The compiler turns every nested class into a separate .class file whose name contains a $:

  • Town$Street.class for a member inner class or a static nested class Street;
  • Town$1Street.class for a local class Street (the number distinguishes same-named local classes in different methods);
  • Food$1.class, Food$2.class for anonymous classes, numbered in order of appearance.

For inner classes, the compiler passes the enclosing instance as a hidden constructor parameter and stores it in a synthetic field. That field is what makes Town.this work. Since Java 11 (JEP 181, "nestmates"), the JVM itself knows that nested classes belong to the same nest, so private access between them no longer requires synthetic accessor methods.

Where Developers Get Tripped Up

1. Creating an inner class from a static context. new Street() inside main fails with "non-static variable this cannot be referenced from a static context" if Street is an inner class. Either provide an outer instance (town.new Street()) or make the class static if it does not need the outer object's state.

2. Modifying a captured local variable. Local and anonymous classes see only effectively final variables. A change made after the class declaration breaks the rule too:

int count = 0;
Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println(count); // compilation error
    }
};
count++; // this line makes count no longer effectively final

3. Memory leaks through the hidden outer reference. Every inner or anonymous class instance created in an instance context holds a reference to its outer object. If that inner object lives long, for example as an event listener, a task in a thread pool or a cache entry, the garbage collector cannot reclaim the outer object either. The rule from "Effective Java" applies: if a nested class does not need access to the enclosing instance, declare it static.

4. Trying to give an anonymous class two supertypes. new Base() implements Runnable { ... } is not valid syntax. If you need a class that extends one type and implements another, declare a named local or member class.

Frequently Asked Questions

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

Yes. Access is symmetric: the outer class can read and write private fields and call private methods of its inner (or static nested) class through a reference to it, and the inner class can access private members of the outer class. The private modifier restricts access at the level of the top-level class, not the nested class.

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

An inner class instance is always tied to an outer instance: it is created with outer.new Inner() and can use the outer object's fields directly. A static nested class has no such link: it is created with new Outer.Nested() and sees only static members of the outer class. Prefer static nested classes whenever the outer instance is not needed.

What does "illegal static declaration in inner class" mean?

It is a compiler error from Java 15 and earlier: inner classes could not declare static members except static final compile-time constants. Since Java 16 (JEP 395) the restriction is gone, so the same code compiles. On older JDKs, move the static member to the outer class or make the nested class static.

Why must local variables referenced from an inner class be final or effectively final?

A local or anonymous class receives a copy of the variable's value, because the object may outlive the method call. If the variable could change later, the copy would be stale. Keeping the variable final or effectively final guarantees the copy and the original always match.

Can an anonymous inner class have a constructor?

No. A constructor must have the class name, and an anonymous class has none. Arguments in new Parent(args) { ... } are passed to the superclass constructor, and any extra setup goes into an instance initializer block inside the anonymous class body.

Can an anonymous class extend a class and implement an interface at the same time?

No. An anonymous class either extends exactly one class or implements exactly one interface. If you need both, or several interfaces, declare a named local class or member inner class instead.

Comments

Please log in or register to have a possibility to add comment.