Inner Classes
In short: A class defined within another class — useful when it’s closely tied in content to the enclosing class and isn’t needed anywhere else.
In more detail: A non-static inner class automatically has access to the fields of the outer instance. For classes that don’t need such access, a static nested class (static class) is usually the cleaner choice, since it can exist independently of an outer instance.
In Depth
class Car {
private String model = "Model X";
class Engine { // non-static inner class
void start() {
// direct access to the outer instance's private field
System.out.println(model + ": engine starting");
}
}
static class Tire { // static nested class
void inflate() {
System.out.println("Tire being inflated"); // NO access to model possible
}
}
}
Car car = new Car();
Car.Engine engine = car.new Engine(); // requires an outer instance
engine.start();
Car.Tire tire = new Car.Tire(); // needs NO Car instance
tire.inflate();The decisive difference: a non-static inner class internally holds an invisible reference to its enclosing instance and therefore can’t exist without it (car.new Engine()) — in return it has full access to its private fields, even without getters. A static nested class, by contrast, behaves practically like a completely normal standalone class that’s only “grouped” for organisation within the outer class, with no access to its instance fields. Rule of thumb: does the nested class need access to the outer instance’s state → non-static; is it self-contained and only serves logical grouping (e.g. a helper type that only makes sense within this one class) → static.
Local classes
Besides inner and static nested classes, there are also local classes — defined directly WITHIN a method, visible only within that method:
class Calculation {
int process(List<Integer> numbers) {
class IntermediateStep { // only exists within this method
int square(int x) { return x * x; }
}
IntermediateStep step = new IntermediateStep();
return numbers.stream().mapToInt(step::square).sum();
}
}Local classes are comparatively rare in modern Java code, since most of their use cases can today be solved more elegantly with lambda expressions or anonymous classes — but they remain useful when a helper class needs several methods or its own state, which a lambda can’t represent.
Practical example: iterator as an inner class
A very common use case for non-static inner classes is implementing a custom iterator for a custom data structure — the iterator needs direct access to the outer class’s internal data:
class CustomList {
private int[] elements;
class MyIterator {
private int position = 0;
boolean hasNext() { return position < elements.length; }
int next() { return elements[position++]; } // access to the outer instance's elements
}
}See also: Classes, Anonymous Classes