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):
- Member inner class (regular inner class)
- Local class (method-local inner class)
- Anonymous inner class
- 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
privateones.
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
thisbecomes the enclosing instance:Street street = new Street(); - Inside a static method of the outer class, such as
main, there is nothis, so you must supply the enclosing instance explicitly with theouter.new Inner()syntax:town.new Street()ornew Town().new Street(). The simple nameStreetis 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,privateor default (package-private) accessfinal, which prevents subclassingabstractstatic, which turns it into a static nested class, which is no longer an inner classstrictfp(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,privateorstatic. Like a local variable, it has no access modifier. - A local class can be
abstractorfinal. - It can use local variables and parameters of the enclosing method only if they are
finalor 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;
thismust refer to the implementing object itself. In an anonymous classthisis 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 staticones. 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.Entryand theBuilderclass 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.classfor a member inner class or a static nested classStreet;Town$1Street.classfor a local classStreet(the number distinguishes same-named local classes in different methods);Food$1.class,Food$2.classfor 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