Class initialization executes static field initializers and static initializer blocks before certain active uses of a class, with ordering rules distinct from object construction.
Java class initialization: static state and failure timing
Java 8+. The program uses only JDK classes and runs without a framework.
A constant is not a service startup hook
A compile-time constant can be inlined into a caller. Reading that constant does not necessarily initialize the declaring class. The program prints a fixed limit before accessing a non-constant configuration field; the initialization message appears only at the latter access.
Static field initializers and blocks execute in their source order during initialization. Calling a static method can also trigger initialization. Constructing an instance adds another sequence: class initialization first when required, then the object’s superclass and instance initialization rules.
Keep static initialization small and predictable. Opening sockets, reading credentials or registering remote dependencies in a static field can turn an ordinary first method call into an unexpected network operation. An explicit application startup step gives failure handling and retries a named boundary.
Loading a class is not initializing it
A class can be loaded or linked before its initialization is required. Reflection APIs can request initialization or avoid it depending on the operation. Do not infer that a classpath scan has run every static block, or that a static block is safe just because startup seemed successful on one machine.
An exception escaping initialization can cause ExceptionInInitializerError and leave that class erroneous for subsequent active uses through that defining loader. Retrying the same method does not simply rerun a failed initializer. This is why configuration validation belongs at an explicit boundary with a defined failure policy, not inside a hidden global expression.
Working program
public class ConfigurationStartup {
static class Limits {
static final int FIXED_BATCH = 20;
static final int LOADED_BATCH = readBatch();
static int readBatch() {
System.out.println("Limits initialized");
return 30;
}
}
public static void main(String[] args) {
System.out.println("fixed=" + Limits.FIXED_BATCH);
System.out.println("loaded=" + Limits.LOADED_BATCH);
System.out.println("again=" + Limits.LOADED_BATCH);
}
}Output
fixed=20
Limits initialized
loaded=30
again=30Cost and failure boundaries
Initialization in this example performs O(1) work and stores two small values. Real static caches retain memory while their classes remain reachable through their loaders. Large eager data structures can increase startup time and retained memory even when a request never uses them.
The runtime coordinates initialization of a class, but that does not make all later operations on a mutable static object safe. A static ArrayList still needs an ownership rule. Circular dependencies among initialization routines can also expose surprising states or locking problems. Read static and final ownership rather than treating global fields as free shared storage.
Common Mistakes
- Do not assume reading an inlined constant runs a static initializer.
- Do not hide slow external work inside first-use initialization.
- Do not confuse initialized static state with thread-safe mutable state.
