Static Initializers
Static initializer blocks (static { ... }) run once when a class is loaded by the JVM, before any instances are created or static methods called. They initialize static fields that require complex logic.
Static initializer = the 'grand opening' ribbon-cutting for a store. Happens exactly once, before any customer enters. If the ribbon-cutting catches fire, the store is permanently condemned.
Class initialization is triggered by: creating an instance, calling a static method, reading/writing a static field, or reflection. The JVM guarantees class initialization is thread-safe — only one thread performs it; others block. Static initializers run in textual order. If a static initializer throws an unchecked exception, the class enters a failed initialization state and subsequent attempts to use it throw ExceptionInInitializerError wrapping the original.
The initialization-on-demand holder idiom exploits JVM class initialization guarantees to implement lazy, thread-safe singletons without synchronization: a nested private static class holds the singleton instance in a static final field; the outer class triggers the inner class's initialization only when getInstance() is first called. Circular class initialization (A's static initializer triggers loading of B which triggers loading of A) can cause partially initialized classes to be visible, leading to NullPointerExceptions on static fields that haven't been set yet.
Static initializer blocks run once at class initialization time and are guaranteed by the JVM to be executed by exactly one thread (others block), making them thread-safe. A static initializer that throws will put the class in a permanently failed state — ExceptionInInitializerError is thrown on first access and NoClassDefFoundError on all subsequent accesses. The initialization-on-demand holder pattern exploits this guarantee for zero-overhead lazy singleton initialization.
If a static initializer throws a RuntimeException, the class can never be initialized again in that JVM run — subsequent attempts throw NoClassDefFoundError, not the original exception, making debugging confusing.