When a class is actually initialised

Core Java · lesson 40 of 42 · 6 min read

Constants inline at compile time, and a static cycle yields a null you can observe.

Open this lesson in the learning hub

Key points

  • A class is initialised on first active use: a new instance, a static method call, or a non-constant static field read. Merely referencing the type does not trigger it.
  • A static final primitive or String initialised with a constant expression is a compile-time constant. The value is inlined into the caller bytecode, so reading it never initialises the class at all.
  • That has a real consequence for deployment: changing such a constant requires recompiling every class that reads it, not just the one that declares it.
  • Static initialisers run in textual order, so a static field read before its own declaration sees the default value - zero or null - rather than the assigned one.
  • A circular static dependency between two classes produces an observable null. Neither class is wrong on its own; the cycle decides which one loses, and the outcome depends on which is touched first.
  • Initialisation is thread-safe and happens once, which is what makes the holder idiom the cleanest lazy singleton - the JVM does the locking for you.

Example

public class InitOrder {

    static class Config {
        // Compile-time constant: INLINED into callers. Reading it does
        // NOT initialise Config, so the static block below never runs.
        static final int MAX = 100;

        // NOT a constant - the value is not known at compile time.
        static final Integer BOXED = 100;

        static { System.out.println("  [Config initialised]"); }
    }

    static class Ordering {
        static int a = compute("a", 1);
        static int b;
        static {
            // b is read BEFORE its declaration below -> still the default 0.
            System.out.println("  in static block, b = " + b);
            b = 2;
        }
        static int c = compute("c", 3);

        static int compute(String name, int v) {
            System.out.println("  computing " + name);
            return v;
        }
    }

    // A circular static dependency. One side WILL see null.
    static class A { static final String NAME = "A"; static final String FROM_B = B.NAME; }
    static class B { static final String NAME = "B"; static final String FROM_A = A.NAME; }

    // The holder idiom: lazy, thread-safe, no synchronisation written by you.
    static class Heavy {
        private Heavy() { System.out.println("  [Heavy constructed]"); }
        private static class Holder { static final Heavy INSTANCE = new Heavy(); }
        static Heavy get() { return Holder.INSTANCE; }
    }

    public static void main(String[] args) {
        System.out.println("read Config.MAX (compile-time constant):");
        System.out.println("  MAX = " + Config.MAX + "   <- no init message above");

        System.out.println("read Config.BOXED (not a constant):");
        System.out.println("  BOXED = " + Config.BOXED);

        System.out.println("static initialiser order:");
        System.out.println("  a=" + Ordering.a + " b=" + Ordering.b + " c=" + Ordering.c);

        System.out.println("circular static dependency:");
        System.out.println("  A.FROM_B = " + A.FROM_B);
        System.out.println("  B.FROM_A = " + B.FROM_A + "   <- one side loses");

        System.out.println("holder idiom (constructed only now):");
        Heavy.get();
    }
}

A static final constant is inlined into callers, and a static cycle leaves one side observing null - order of first touch decides which.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Core Java course, and every lesson in it is listed on the Core Java contents page.