skip to content

How does static initialization run across a class hierarchy, and exactly when is a superclass's static initialization triggered relative to a subclass's?

level: seniorimportance: should knowfreq 45%

answer

  1. parent statics before child statics
  2. each class initialized at most once
  3. inherited static access initializes only the declaring class
  4. constant-expression static final is inlined, no init
  5. holder idiom = lazy thread-safe singleton

basics

~20 s

A class's static initialization runs once, and a superclass is always initialized before its subclass. So when a subclass is first used, the JVM initializes the parent's statics first, then the child's. Each happens at most once.

solid answer

~50 s

Static initialization is per-class and runs at most once, triggered by the first active use of that specific class. The JLS guarantees a superclass is fully initialized before its subclass: when the JVM initializes a class, it first recursively initializes the direct superclass (and superinterfaces with default methods), then runs that class's static field initializers and static blocks in textual order. Crucially, merely initializing a subclass does NOT initialize the subclass purely from accessing an inherited static member — accessing a static field declared in the parent triggers only the parent's initialization, not the child's. Also, reading a compile-time constant (static final of a primitive/String with a constant expression) triggers no initialization at all, because the value is inlined at compile time. These rules explain surprising output ordering and are the basis of the initialization-on-demand holder idiom for lazy singletons.

go deeper

for a junior

Knows the parent's statics run before the child's.

for a middle

Knows static init is once-per-class and triggered by first use, parent before child.

for a senior

Distinguishes inherited-static access (declaring class only) and constant inlining, and applies the holder idiom.

for a principal

Designs lazy/singleton/registry initialization around JLS init semantics and reasons about init-lock deadlocks and cyclic static dependencies.

## Core terms *Static initialization* = running a class's static field initializers and `static { }` blocks. It happens **once per class**, the first time the class is *initialized*. *Class initialization* is a distinct JVM phase that follows loading and linking; the JVM does it **lazily**, on first active use. ## What counts as "first active use" (triggers initialization) Per the JLS, a class T is initialized on the first of these: - creating an instance of T (`new T()`), - invoking a static **method** declared in T, - accessing or assigning a static **field** declared in T (unless it's a constant — see below), - reflective initialization (e.g. `Class.forName`). ## The hierarchy rule When the JVM initializes a class, it must **first initialize the direct superclass** (recursively up to `Object`), and any superinterface that declares a default method. Only after the parents are done does it run **this class's** static initializers, in textual source order. So for `new Child()` (assuming neither is initialized yet), static order is: ``` Parent static fields + static blocks (textual order) Child static fields + static blocks (textual order) ``` ...and only then does instance construction begin. ```java class Parent { static { System.out.println("Parent static"); } } class Child extends Parent { static { System.out.println("Child static"); } } // new Child() --> prints "Parent static" then "Child static" ``` ## The subtle traps 1. **Accessing an inherited static field does NOT initialize the subclass.** A static field declared in the parent "belongs to" the parent; referencing `Child.parentStaticField` initializes **Parent only**, not Child. ```java class Parent { static int X = log("Parent"); } class Child extends Parent { static int Y = log("Child"); } // reading Child.X --> only "Parent" prints; Child is NOT initialized ``` 2. **Compile-time constants trigger no initialization at all.** A `static final` of a primitive or `String` whose value is a *constant expression* is inlined into callers at compile time, so reading it never loads or initializes the declaring class. ```java class K { static final int N = 10; static { System.out.println("K init"); } } // reading K.N --> "K init" does NOT print; N is inlined as 10 ``` If you make it `static final int N = computeTen();` (not a constant expression), it is *not* inlined and accessing it DOES initialize K. ## Why this matters: the holder idiom Because a nested class is initialized only on first use, and the JVM guarantees class initialization is thread-safe (it holds an init lock), you get a clean lazy, thread-safe singleton: ```java class Service { private Service() {} private static class Holder { static final Service INSTANCE = new Service(); } static Service get() { return Holder.INSTANCE; } // Holder initialized on first get() } ``` `Holder` (and thus `INSTANCE`) is not created until `get()` first runs, and the JVM serializes that initialization — no `synchronized` needed. ## Summary - Parent statics before child statics, each at most once. - Triggered by first active use of *that specific* class. - Inherited-static access initializes the declaring class only. - Constant-expression `static final` inlines and triggers nothing.

  • Does reading a public static final int CONSTANT = 5 from another class initialize the class that declares it?
    No. A constant expression of a primitive or String is inlined at compile time, so the declaring class is never loaded or initialized just for that read.
  • Why is the initialization-on-demand holder idiom thread-safe without synchronized?
    The JVM guarantees a class is initialized exactly once under an internal init lock; placing the instance in a nested holder class defers and serializes its creation to first use.

saying these in an interview costs you the question

  • Thinking accessing an inherited static field initializes the subclass
  • Assuming a static final constant access triggers class initialization
  • Believing static init runs at JVM startup rather than on first use
  • Thinking child statics can run before parent statics

context