When a class is initialized in Java
Lazy initialization on first active use; static init order.
Loaded is not initialized
A class is initialized lazily, on its first active use: creating an instance with new, calling a static method, reading or writing a non-constant static field, or Class.forName(name). Then its static initializers and static assignments run once, top to bottom in source order.
Constant or not?
What does this print?
class Cfg {
static { System.out.println("init"); }
static final String NAME = "app";
static String mode() { return "dev"; }
}
void main() {
System.out.println(Cfg.NAME);
System.out.println(Cfg.mode());
}init app devapp init devapp devinit app init dev
Show the answer
NAME is a compile-time constant (static final with a constant value), so javac copies "app" straight into main; Cfg isn't touched. Calling mode() is an active use, so init prints just before dev.
What does NOT trigger it
These leave the class uninitialized: a class literal like Foo.class, creating an array like new Foo[10] (that only makes the array type) and reading a compile-time constant. By contrast, Class.forName("app.Config") loads and initializes by default.
var c = Foo.class; // no init
var arr = new Foo[10]; // no init
Class.forName("app.Foo"); // init!Parents go first
What does this print?
class Base {
static { System.out.println("Base"); }
}
class Kid extends Base {
static { System.out.println("Kid"); }
}
void main() {
new Kid();
}Kid BaseBase KidKid
Show the answer
A superclass is initialized before its subclass, so Base prints first. Careful: if you only read a static field *declared* in Base through Kid, like Kid.n, only Base is initialized.
Reading a field too early
Initializers run top to bottom. When max = calc() runs, base has only its default value 0 from the preparation step, so max ends up 0, not 200. Only afterwards is base set to 100.
class Limits {
static int max = calc(); // 0 * 2 = 0
static int base = 100;
static int calc() { return base * 2; }
}The lazy holder singleton
Lazy init powers a classic idiom: put the instance in a nested Holder class. It's created only when get() first touches Holder, and the JVM guarantees initialization runs once even under thread races, with no synchronized needed. Old JDBC code used Class.forName("...Driver") just to run a driver's static block.
Key takeaways
- Triggers: new, static method call, non-constant static field, Class.forName
- Not triggers: Foo.class, new Foo[10], compile-time constants
- A superclass is initialized before its subclass
- Static initializers run once, in textual order
💡 A class is like a shop that opens its doors only when the first customer walks in, not when it appears on the map.
The JVM guarantees a class's static initialization runs exactly once, even when many threads race to use it first. Other threads simply wait until it finishes.
Practice questions
What does this print?
class A {
static { System.out.println("A init"); }
static final int X = 5;
static int y = 7;
}
void main() {
System.out.println(A.X);
System.out.println("--");
System.out.println(A.y);
}- A init 5 -- A init 7
- 5 -- 7
- A init 5 -- 7
- 5 -- A init 7
Check your answer
5 -- A init 7. A.X is a compile-time constant, so javac copies 5 into main and A is not touched. Reading A.y is an active use, so A is initialized right before 7 is printed.
What does this print?
class P {
static int v = 1;
static { System.out.println("P init"); }
}
class Ch extends P {
static { System.out.println("Ch init"); }
}
void main() {
System.out.println(Ch.v);
}- P init 1
- 1
- P init Ch init 1
- Ch init P init 1
Check your answer
P init 1. v is declared in P, so Ch.v is really an access to P.v. Only P is initialized; Ch's static block never runs.