Back to Blog
Java

Java Static Initialization Order Explained

java static initialization order: Understand the JVM's static initialization order: superclass first, then fields and static blocks in textual order, with edge cases l...

static initializationclass loadingJVMstatic fieldsinitialization order
Illustration of Java class initialization order showing superclass before static fields and blocks in textual sequence.

The java static initialization order determines when static fields and static initializer blocks execute when a class is first loaded. This order is not arbitrary: the JVM follows a defined sequence that affects how you structure static dependencies. Misunderstanding it leads to subtle bugs where fields appear to hold default values or where initialization happens later than expected.

The Sequence the JVM Follows for Class Initialization

Class initialization is the final phase of class loading. Before a class is initialized, the JVM loads it, verifies its bytecode, and prepares static fields by assigning default values (zero, null, false). The actual initialization phase runs only once, when the class is first actively used. Active uses include creating a new instance, accessing a static field (unless it is a compile-time constant), calling a static method, or invoking reflection that triggers initialization.

During initialization, the JVM executes the class's static initializer, which is the combined code from all static field initializers and static blocks in the order they appear in the source file. But before that, the JVM initializes the superclass. This guarantees that a subclass never observes an uninitialized superclass state.

The following table summarizes the initialization order for a typical class hierarchy:

StepAction
1Load and link the class (verify, prepare, resolve)
2Initialize the direct superclass (recursively)
3Execute static field initializers and static blocks in textual order
4Mark the class as initialized

Interfaces follow a slightly different rule, covered later.

Static Fields and Static Blocks Run in Textual Order

Within a single class, the JVM executes static field initializers and static blocks in the exact order they appear in the source file. Consider this example:

public class OrderDemo { static int first = assign("first", 1); static { System.out.println("block A"); } static int second = assign("second", 2); static { System.out.println("block B"); } static int assign(String name, int value) { System.out.println(name + " = " + value); return value; } public static void main(String[] args) { // Accessing a static field triggers initialization System.out.println(OrderDemo.first); } }

When main runs, the JVM initializes OrderDemo. The output is:

first = 1
block A
second = 2
block B
1

Each static field assignment and each static block contributes to the same initialization routine. If a static block references a field declared later, that field still holds its default value at that point, because the later assignment has not executed yet. This is why forward references using simple names are compile-time errors; you can work around it with a qualified name, but doing so often masks a design problem.

Superclass Initialization Happens Before Subclass Fields

The JVM initializes a class's direct superclass before executing the subclass's own static initializer. This rule applies recursively, so the entire superclass chain initializes from the top down.

class Parent { static { System.out.println("Parent initialized"); } } class Child extends Parent { static int value = 10; static { System.out.println("Child initialized"); } } public class HierarchyDemo { public static void main(String[] args) { System.out.println(Child.value); } }

Accessing Child.value triggers initialization of Child, but before that, the JVM initializes Parent. The output is:

Parent initialized
Child initialized
10

This ordering ensures that static state in a superclass is ready before a subclass relies on it. If a subclass's static block calls a superclass method that depends on superclass static fields, those fields are already assigned.

Interface Initialization Differs from Class Initialization

Interfaces are initialized under different conditions. An interface is initialized when a static field declared in it is accessed, or when a static method of the interface is invoked (Java 8 and later). However, initializing a class that implements an interface does not initialize that interface unless the class actually uses the interface's static members.

interface Config { int MAX = computeMax(); static int computeMax() { System.out.println("Config initialized"); return 100; } } class Service implements Config { static { System.out.println("Service initialized"); } } public class InterfaceDemo { public static void main(String[] args) { System.out.println(Service.class); // Does not trigger Config init System.out.println(Config.MAX); // Triggers Config init } }

Here, loading Service does not initialize Config. Only when Config.MAX is accessed does the interface initialize. When an interface is initialized, its superinterfaces are initialized first, following the same depth-first rule used for classes.

Forward References and Circular Dependencies

Static initialization order becomes tricky when classes depend on each other. A forward reference within the same class using a simple name is a compile-time error because the field is not yet declared. But you can reference another class's static field, which may trigger that class's initialization. If two classes initialize each other, the JVM detects that a class is already being initialized by the current thread and allows it to proceed with default values.

class A { static int value = B.other + 1; static { System.out.println("A initialized, B.other = " + B.other); } } class B { static int other = A.value + 1; static { System.out.println("B initialized, A.value = " + A.value); } } public class CircularDemo { public static void main(String[] args) { System.out.println(A.value); } }

When A.value is accessed, the JVM starts initializing A. The static field value references B.other, which triggers initialization of B. During B's initialization, A.value is referenced, but A is already being initialized by the same thread, so the JVM does not restart it; A.value still holds its default value 0. The output shows the default value, not a recursive initialization loop. This behavior is defined by the Java Language Specification and is something to avoid in real code because it produces surprising results.

Compile-Time Constants Are Inlined and Skip Initialization

A static final field initialized with a compile-time constant expression (a primitive or String literal, or a constant expression) is inlined by the compiler. Accessing such a field does not trigger class initialization, because the value is known at compile time and is copied into the referencing class's constant pool.

class Constants { static final int MAX = 100; // compile-time constant static final String NAME = "demo"; // compile-time constant static final long RANDOM = System.nanoTime(); // not a constant } public class ConstantDemo { public static void main(String[] args) { System.out.println(Constants.MAX); // No initialization System.out.println(Constants.NAME); // No initialization System.out.println(Constants.RANDOM); // Triggers initialization } }

Only RANDOM triggers initialization because its value is not known at compile time. This distinction matters when you rely on a static block to set up resources: if a field is a compile-time constant, that setup may never run when the constant is accessed.

Thread Safety and Runtime Cost of Static Initialization

The JVM guarantees that class initialization is performed exactly once and is thread-safe. When multiple threads access a class simultaneously, only one thread executes the static initializer; the others block until it completes. This synchronization has a runtime cost, especially if the static initializer performs heavy work such as reading configuration files, opening network connections, or starting background threads.

A static initializer that blocks on I/O can stall every thread that touches the class for the first time. Worse, if the initializer waits on a resource that another thread must release, and that other thread is blocked waiting for the class to initialize, you get a deadlock. For example, if thread A initializes ClassX and inside its static block waits for a lock held by thread B, while thread B is trying to access ClassX and is blocked on initialization, neither thread can proceed.

To avoid these problems, keep static initializers short and deterministic. If you need to load external data, consider lazy initialization with a holder class or a proper singleton pattern that does not hold locks during I/O. Also be aware that class initialization is not re-entrant: if a static block calls a method that triggers initialization of the same class, the JVM recognizes the in-progress initialization and does not run it again.

Observing Initialization Order in Production

When debugging initialization order issues, you can observe the sequence directly by adding logging statements inside static blocks and static field initializers. In a production environment, you can use a debugger with breakpoints on static initializers, or enable JVM class-loading tracing with the -verbose:class flag. Note that -verbose:class shows class loading, not initialization, so it may not reveal the exact order of static field assignments. For initialization-specific tracing, a debugger or temporary logging is more reliable.

Another practical approach is to isolate the class that exhibits the problem and write a small test that accesses its static members in a controlled order. By checking which static blocks run and when, you can map the actual initialization sequence against your expectations. This is especially useful when a hierarchy involves multiple interfaces and superclasses, because the textual order of fields in each class is only one part of the overall sequence.

java static initialization order: Practical Usage and Code E | RYUSLOG DEV