Java offers developers the ability to group classes and interfaces within other classes through nested types—providing a convenient way to encapsulate functionality and logically structure code.
In this post, we’ll explore both static and non-static nested types, how they work, how to use them effectively, and what happens behind the scenes when they’re compiled. Whether you’re using nested types to create small helper structures or to manage complex hierarchies, understanding how they behave will help you write cleaner, more organized Java programs.
Static Nested Types
The simplest variant of a nested class or interface is placed in the type like a static property and is called a static nested type. These nested types can do the same as “normal” types, but they form a small subpackage with its own namespace. In particular, no outer class objects are needed to create instances of static nested classes following this pattern.
Let’s declare SquirrelNutCaramel as an outer class and Peanut as a static nested class. public
class SquirrelNutCaramel {
static String name = "Squirrel Nut Caramel";
int invented = 1890;
static class Peanut {
void print() {
System.out.println( name );
// System.out.println( invented );
}
}
public static void main( String[] args ) {
Peanut peanut = new Peanut(); // or SquirrelNutCaramel.Peanut peanut = …
peanut.print();
}
}
The static nested class Peanut has access to all other static members of the outer class SquirrelNutCaramel (in our case, to the name variable). Access to object variables isn’t possible from the static nested class because the nested class is considered a separate class that’s located in the same package, and this access wouldn’t be possible in the construction. For example, if you write the following code:
System.out.println( invented );
The compiler will answer this access attempt with an error message: “Non-static field ‘invented’ cannot be referenced from a static context.”
Access from outside to static nested classes can succeed with the notation Outer- Type.NestedType; the dot is thus used in the same way access to static members is formed, using packages as namespaces. A static nested class must have a different name than its outer class.
Modifiers and Visibility
The allowed modifiers include abstract, final, and some visibility modifiers. Normal top-level classes may be package-visible or public; nested classes may also be public or package-visible, or alternatively protected or private. A private static nested class should be understood like a normal private static variable: This class be seen by the enclosing outer class but cannot be seen by other top-level classes. protected with static nested types allows for slightly more efficient bytecode for the compiler but is otherwise not in use.
Records as Containers
In Java, if a method should have more than one return value, you must use a data structure. In this regard, records are quite suitable to act as small containers. Consider the following example:
public class MinMaxDemo {
public record MinMax(int min, int max) {}
public static MinMax minMax( int... values ) {
IntSummaryStatistics stats = IntStream.of( values ).summaryStatistics();
return new MinMax( stats.getMin(), stats.getMax() );
}
}
The implementation of the actual functionality is moved to a Java method.
Implementing Static Nested Types*
The compiler generates normal class files from nested types, but these class files are equipped with what’s called synthetic methods. For nested types, the compiler generates new names according to the pattern OuterType$NestedType. Note how the dollar sign separates the names of outer tape and nested type. The corresponding .class file on your hard drive will then use this name, which is also called the binary name of the nested type. Using this name is important, for example, when loading manually.
Non-Static Nested Types
A non-static nested type is an inner type that is comparable to an object property. Let’s declare an inner class named Room in House with the following example code:
class AlmondJoy {
String name = "Almond Joy";
private int introduced = 1946;
class Coconut {
void print() {
System.out.println( name );
System.out.println( introduced );
}
}
}
An instance of the Coconut class has access to all members of AlmondJoy, including its private members.
Creating Instances of Inner Classes
To create an instance of Coconut, an instance of the outer class must exist first. One important difference in this regard relates to the static nested classes: Static nested types can exist even without an object of the outer class.
In a constructor or in an object method of the outer class, an instance of the inner class can be created simply via the new keyword. If you are coming from outside the outer class (or from a static block of the outer class) and you want to create instances of the inner class, for non-static nested classes, you must ensure that an instance of the outer class is available. Java prescribes a special form for the creation via new, which has the following general format:
reference.new InnerClass(…)
In this syntax, reference is a reference of the type of the outer class. To create an object in the static main(String[]) method of the house, you can write the following code:
AlmondJoy almondJoy = new AlmondJoy();
Coconut coconut = almondJoy.new Coconut();
This code can even be reduced to one line:
Coconut coconut = new AlmondJoy().new Coconut();
The this Reference
If an inner class In wants to access the this reference of its surrounding class Out, you must write Out.this. If variables of the inner class overlap variables of the outer class, you would write Out.this.property to access the members of the outer class Out.
class FurnishedHouse {
String s = "House";
class Room {
String s = "Room";
class Chair {
String s = "Chair";
void print() {
System.out.println( s ); // Chair
System.out.println( this.s ); // Chair
System.out.println( Chair.this.s ); // Chair
System.out.println( Room.this.s ); // Room
System.out.println( FurnishedHouse.this.s ); // House
}
}
}
public static void main( String[] args ) {
new FurnishedHouse().new Room().new Chair().print();
}
}
Note: Non-static nested classes can be nested arbitrarily, but since the name is unique, you can always get to the particular property via classname.this.
Considering our earlier examples, objects for the inner classes Room and Chair can be created in the following ways:
FurnishedHouse h = new FurnishedHouse(); //
Instance of FurnishedHouse
FurnishedHouse.Room r = h.new Room(); // Instance of Room in h
FurnishedHouse.Room.Chair c = r.new Chair(); // Instance of Chair in r
c.print(); // Method of Chair
The qualification with the dot in FurnishedHouse.Room.Chair doesn’t automatically mean that FurnishedHouse is a package with the Room subpackage in which the Chair class exists. The double use of the dot doesn’t really improve readability, and you risk creating confusion between inner classes and packages. For this reason, a naming convention should be followed: Class names should start with uppercase letters; package names, with lowercase letters.
Class Files Generated by the Compiler*
For our House and Room example, the compiler creates the House.class and House$Room.class files. For the inner class to access the object variables of the outer class, the compiler automatically generates a reference to the associated object of the outer class in each instance of the inner class. As a result, the inner class can also access object variables of the outer class. For the inner class, you’ll have the House$Room.class file with the following code:
class House$Room {
final House this$0;
House$Room( House house ) {
this$0 = house;
}
…
}
The this$0 variable references the House.this instance (i.e., the associated outer class). The constructors of the inner class get an additional parameter of type House to initialize the this$0 variable. Since you don’t get to see the constructors anyway, it’s irrelevant.
Editor’s note: This post has been adapted from a section of the book Java: The Comprehensive Guide by Christian Ullenboom. Christian is an Oracle-certified Java programmer and has been a trainer and consultant for Java technologies and object-oriented analysis and design since 1997.
This post was originally published 7/2025.
Comments