☕ Java

Initialization Blocks

Initialization blocks are code blocks that run during object or class creation, supplementing constructors and field initializers. Java provides two kinds: instance initialization blocks, which run every time an object is created before the constructor body executes, and static initialization blocks, which run exactly once when the class is first loaded by the JVM. Together with field initializers, they form a complete picture of how Java objects and classes come to their initial state. This entry covers the execution order of all initialization mechanisms, the purpose and use cases for each type of block, interaction with constructors and inheritance, and the practical scenarios where initialization blocks solve problems that field initializers and constructors cannot.

Instance Initialization Blocks

An instance initialization block is a block of code written directly inside a class body, outside any method or constructor, without the static keyword. It is enclosed in braces and runs every time an object of the class is created. The compiler copies the instance initialization block's code into every constructor — the block runs before the constructor body executes but after the super() call completes. Instance initialization blocks exist to share initialization code across multiple constructors without extracting it into a method. When a class has several constructors that all need to execute the same setup code — registering a listener, seeding a collection, writing an audit entry — the code can be placed in an instance initializer once rather than duplicated in each constructor or extracted into a private helper method that every constructor calls. A class can declare multiple instance initialization blocks. They run in the order they appear in the source file, all before any constructor body. This means the combined effect of all initialization blocks runs first, and then each constructor has the opportunity to perform its own specific setup on top of that shared foundation. The order of execution during object creation is precisely defined: first, the superclass constructor chain runs all the way to Object; then, in the current class, field initializers and instance initialization blocks run top-to-bottom in the order they appear; finally, the rest of the constructor body runs.
Java
// ── Instance initialization block ────────────────────────────────────
public class AuditedEntity {

    private final String     entityId;
    private final List<String> changeLog;
    private final Instant    createdAt;

    // ── Instance initializer — runs for EVERY constructor ─────────────
    // Runs after super() but BEFORE each constructor body
    {
        this.changeLog = new ArrayList<>();
        this.createdAt = Instant.now();
        changeLog.add("Entity created at " + createdAt);
        System.out.println("Instance initializer ran");
    }

    // ── Constructor 1 — receives entityId ─────────────────────────────
    public AuditedEntity(String entityId) {
        // Instance initializer already ran — changeLog and createdAt set
        this.entityId = entityId;
        changeLog.add("Assigned id: " + entityId);
    }

    // ── Constructor 2 — generates a random entityId ───────────────────
    public AuditedEntity() {
        // Same instance initializer ran here too
        this.entityId = UUID.randomUUID().toString();
        changeLog.add("Auto-assigned id: " + entityId);
    }

    public List<String> getChangeLog() {
        return Collections.unmodifiableList(changeLog);
    }
}

// ── Multiple instance initializers — run in order ─────────────────────
public class MultiInitDemo {

    private int x;
    private int y;

    { x = 1; System.out.println("First block: x=" + x); }   // runs first

    { y = 2; System.out.println("Second block: y=" + y); }  // runs second

    public MultiInitDemo() {
        System.out.println("Constructor: x=" + x + " y=" + y);
    }
}
// Output when new MultiInitDemo():
// First block: x=1
// Second block: y=2
// Constructor: x=1 y=2

// ── Exact execution order ─────────────────────────────────────────────
public class Parent {
    { System.out.println("Parent instance initializer"); }
    public Parent() { System.out.println("Parent constructor"); }
}

public class Child extends Parent {
    { System.out.println("Child instance initializer"); }
    public Child() {
        // super() implicit — runs Parent chain first
        System.out.println("Child constructor");
    }
}

// new Child() output:
// Parent instance initializer   ← parent's initializer
// Parent constructor            ← parent's constructor body
// Child instance initializer    ← child's initializer
// Child constructor             ← child's constructor body

Static Initialization Blocks

A static initialization block is a block of code prefixed with the static keyword, written directly inside the class body outside any method. It runs exactly once: when the class is first loaded by the JVM, before any instance is created or any static method is called. Multiple static blocks run in the order they appear in the source file. Static initialization blocks exist to initialize static fields that cannot be initialized with a simple expression. A static field that requires a try-catch, a loop, a conditional, or multiple statements cannot be initialized in the field declaration — that syntax accepts only a single expression. The static block provides a place to write arbitrarily complex initialization logic for static state. Class loading is triggered by the first action that requires the class to be present in memory: creating an instance, calling a static method, reading a static field, loading a subclass, or explicitly loading with Class.forName(). The static initialization block runs once at this point and never again — Java's class loading guarantee ensures that even in multithreaded programs, the static block runs exactly once and other threads see a fully initialized class. The JVM guarantees that static initialization is thread-safe. If two threads simultaneously trigger the loading of the same class, one will run the static block and the other will wait. After the static block completes, both threads see the fully initialized class. This guarantee is the foundation of the static initialization holder singleton pattern, which achieves lazy, thread-safe initialization without any synchronization in the access path.
Java
// ── Static initialization block ──────────────────────────────────────
public class CountryCodeLookup {

    // ── Static field requiring complex initialization ──────────────────
    private static final Map<String, String> DIAL_CODES;
    private static final Set<String>         VALID_CODES;

    // ── Static initializer — runs ONCE when class is loaded ───────────
    static {
        System.out.println("CountryCodeLookup class loading...");

        Map<String, String> codes = new HashMap<>();
        codes.put("US", "+1");
        codes.put("GB", "+44");
        codes.put("DE", "+49");
        codes.put("FR", "+33");
        codes.put("JP", "+81");
        codes.put("CN", "+86");
        codes.put("AU", "+61");
        codes.put("IN", "+91");

        DIAL_CODES   = Collections.unmodifiableMap(codes);
        VALID_CODES  = Collections.unmodifiableSet(codes.keySet());

        System.out.println("Loaded " + DIAL_CODES.size() + " country codes");
    }

    public static String getDialCode(String countryCode) {
        return DIAL_CODES.getOrDefault(countryCode.toUpperCase(), "unknown");
    }

    public static boolean isValidCode(String code) {
        return VALID_CODES.contains(code.toUpperCase());
    }
}

// ── Static block with error handling ─────────────────────────────────
public class DatabaseConfig {

    private static final Properties DB_PROPS;

    static {
        DB_PROPS = new Properties();
        try (InputStream in = DatabaseConfig.class
                .getResourceAsStream("/db.properties")) {
            if (in == null) {
                throw new ExceptionInInitializerError(
                    "Cannot find /db.properties on classpath");
            }
            DB_PROPS.load(in);
        } catch (IOException e) {
            // Wrap checked exception — static block cannot throw checked
            throw new ExceptionInInitializerError(e);
        }
        System.out.println("Database config loaded: " +
            DB_PROPS.getProperty("db.url"));
    }

    public static String getUrl()      { return DB_PROPS.getProperty("db.url");      }
    public static String getUsername() { return DB_PROPS.getProperty("db.username"); }
}

// ── Static initialization holder — lazy, thread-safe singleton ────────
public class ExpensiveSingleton {

    private ExpensiveSingleton() {
        System.out.println("Singleton created — expensive setup...");
    }

    // Holder class NOT loaded until getInstance() is first called
    private static class Holder {
        // Static block in Holder runs when Holder is first loaded
        static final ExpensiveSingleton INSTANCE = new ExpensiveSingleton();
    }

    // No synchronization needed — JVM class loading is thread-safe
    public static ExpensiveSingleton getInstance() {
        return Holder.INSTANCE;   // triggers Holder class loading
    }
}

Initialization Order — The Complete Picture

Understanding the complete initialization order is essential for debugging unexpected null values, wrong initial states, and NullPointerExceptions that appear during construction. Java's initialization sequence follows a strict order that is deterministic and knowable. The rule is: static before instance, superclass before subclass, declaration order within the same level. When a class is loaded, the following sequence occurs: static fields are zeroed (all numeric types to 0, references to null, booleans to false); then static field initializers and static blocks run in declaration order; the class is now ready for use. When an instance is created: instance fields are zeroed; then the constructor chain runs upward to Object (each super() call), and as each level in the chain completes, that level's field initializers and instance blocks run before the constructor body executes. The zeroing step is important and often overlooked. Every field has a guaranteed initial value before any initializer or constructor runs. int fields start at 0, boolean fields at false, reference fields at null. Initializers then overwrite these zeros with the declared initial values. This zeroing guarantee means Java code can never read a truly uninitialized variable — the language eliminates the class of bugs where C programs crash due to reading garbage memory.
Java
// ── Complete initialization order demonstration ────────────────────────
public class InitOrderDemo {

    // ── 1. Static field initializer ───────────────────────────────────
    private static int staticField = initStatic("static field");

    // ── 2. Static block ───────────────────────────────────────────────
    static {
        System.out.println("Static block: staticField=" + staticField);
        staticField = 100;
    }

    // ── 3. Another static field after the block ───────────────────────
    private static int staticField2 = initStatic("static field 2");

    // ── 4. Instance field initializer ────────────────────────────────
    private int instanceField = initInstance("instance field");

    // ── 5. Instance block ────────────────────────────────────────────
    {
        System.out.println("Instance block: instanceField=" + instanceField);
        instanceField = 200;
    }

    // ── 6. Another instance field after the block ─────────────────────
    private int instanceField2 = initInstance("instance field 2");

    // ── 7. Constructor body ───────────────────────────────────────────
    public InitOrderDemo() {
        System.out.println("Constructor: instanceField=" +
            instanceField + " instanceField2=" + instanceField2);
    }

    private static int initStatic(String name) {
        System.out.println("Static initializer: " + name);
        return 1;
    }

    private int initInstance(String name) {
        System.out.println("Instance initializer: " + name);
        return 2;
    }
}

// Output of: new InitOrderDemo(); new InitOrderDemo();
//
// (First time class is loaded):
// Static initializer: static field       ← step 1
// Static block: staticField=1            ← step 2
// Static initializer: static field 2    ← step 3
//
// (First object construction):
// Instance initializer: instance field  ← step 4
// Instance block: instanceField=2       ← step 5
// Instance initializer: instance field 2← step 6
// Constructor: instanceField=200 instanceField2=2  ← step 7
//
// (Second object construction — NO static steps again):
// Instance initializer: instance field  ← step 4 repeats
// Instance block: instanceField=2       ← step 5 repeats
// Instance initializer: instance field 2← step 6 repeats
// Constructor: instanceField=200 instanceField2=2  ← step 7 repeats

// ── Zeroing guarantee ─────────────────────────────────────────────────
public class ZeroingDemo {
    static int  staticInt;          // guaranteed 0
    static boolean staticBool;      // guaranteed false
    static String staticRef;        // guaranteed null

    int  instanceInt;               // guaranteed 0 before initializer
    boolean instanceBool;           // guaranteed false before initializer
    Object instanceRef;             // guaranteed null before initializer

    {
        System.out.println("Before assignment: " +
            instanceInt + " " + instanceBool + " " + instanceRef);
        // prints: 0 false null
    }
}

Practical Use Cases and Best Practices

Static initialization blocks have several important real-world applications. Loading configuration files, registering JDBC drivers, populating lookup tables, initializing logging frameworks, loading native libraries with System.loadLibrary(), and constructing complex immutable static data structures all benefit from static blocks because they require either multiple statements, exception handling, or control flow — none of which fit in a field declaration. Instance initialization blocks are less commonly needed in modern Java because most of their use cases can be handled by factory methods, builder patterns, or constructor chaining. Their main remaining value is in anonymous inner classes, which cannot have named constructors. An anonymous class that needs initialization logic must use an instance block. The best practice guidance is: use static blocks for complex static initialization that cannot be expressed as a field initializer; avoid static blocks for logic that should be lazy (prefer the initialization holder pattern); keep initialization blocks focused and short; prefer constructors for initialization logic that varies between construction paths; and never call overridable instance methods from an initialization block or constructor — the subclass method may execute before the subclass fields are initialized.
Java
// ── Use case 1: Loading native library with error handling ───────────
public class NativeEncryption {

    private static final boolean NATIVE_AVAILABLE;

    static {
        boolean available;
        try {
            System.loadLibrary("encryption-native");
            available = true;
            System.out.println("Native encryption library loaded");
        } catch (UnsatisfiedLinkError e) {
            available = false;
            System.err.println("Native library unavailable — using Java fallback");
        }
        NATIVE_AVAILABLE = available;
    }

    public byte[] encrypt(byte[] data) {
        if (NATIVE_AVAILABLE) return nativeEncrypt(data);
        else                  return javaEncrypt(data);
    }

    private native byte[] nativeEncrypt(byte[] data);
    private byte[] javaEncrypt(byte[] data) { /* pure Java impl */ return data; }
}

// ── Use case 2: Registering handlers at class load time ──────────────
public class EventHandlerRegistry {

    private static final Map<String, EventHandler> HANDLERS;

    static {
        Map<String, EventHandler> h = new LinkedHashMap<>();
        // Register in priority order — LinkedHashMap preserves insertion order
        h.put("auth",    new AuthEventHandler());
        h.put("payment", new PaymentEventHandler());
        h.put("audit",   new AuditEventHandler());
        h.put("notify",  new NotificationEventHandler());
        HANDLERS = Collections.unmodifiableMap(h);
    }

    public static EventHandler getHandler(String type) {
        EventHandler handler = HANDLERS.get(type);
        if (handler == null) throw new IllegalArgumentException(
            "No handler for event type: " + type);
        return handler;
    }
}

// ── Use case 3: Instance block for anonymous class initialization ──────
// Anonymous classes cannot have named constructors — use instance blocks
Comparator<String> caseInsensitive = new Comparator<String>() {

    private final Collator collator;

    // Instance block initializes the anonymous class
    {
        collator = Collator.getInstance(Locale.ENGLISH);
        collator.setStrength(Collator.SECONDARY);
    }

    @Override
    public int compare(String a, String b) {
        return collator.compare(a, b);
    }
};

// ── Anti-pattern: calling overridable method in initializer ─────────
public class UnsafeParent {
    {
        // NEVER call overridable methods from initializers or constructors
        // This calls Child.init() BEFORE Child's fields are initialized
        init();   // ← DANGEROUS if overridden
    }

    protected void init() {
        System.out.println("Parent init");
    }
}

public class UnsafeChild extends UnsafeParent {
    private String name = "initialized";

    @Override
    protected void init() {
        // Runs BEFORE "name = initialized" — name is still null here!
        System.out.println("Child init, name=" + name);  // null!
    }
}

Related Topics in Object-Oriented Programming

OOP Concepts
Object-Oriented Programming (OOP) is a programming paradigm that organises software around objects — self-contained units that combine data (fields) and behaviour (methods). Java is a class-based, object-oriented language where almost everything is an object. OOP provides four foundational principles: encapsulation, inheritance, polymorphism, and abstraction. Together they produce software that is modular, reusable, maintainable, and easier to reason about as systems grow in complexity.
Classes
A class is the fundamental building block of Java. It is a blueprint that defines the structure and behaviour of objects — what data each object holds (fields) and what operations it can perform (methods). Every Java program is composed of classes. Understanding how to design a class well — choosing the right access modifiers, separating state from behaviour, and writing cohesive single-responsibility classes — is the foundation of object-oriented programming in Java. This entry covers class anatomy, fields, methods, access modifiers, static vs instance members, the this keyword, and class design principles.
Objects
An object is a runtime instance of a class. Where a class is a blueprint that exists in source code, an object is a living entity that exists in memory during program execution — it has its own identity, its own state stored in its fields, and the ability to respond to method calls. Every object has three fundamental properties: identity (a unique memory address), state (the current values of its fields), and behaviour (the methods it responds to). This entry covers object identity vs equality, the Object class hierarchy, object state and mutation, method calls, toString, equals and hashCode, and the object lifecycle.
Object Creation
Object creation in Java is the process of allocating memory, initialising fields, and running constructor logic to bring an object into existence. The new keyword is the primary mechanism, but Java also provides factory methods, builder patterns, copy constructors, and object cloning. Constructors are special methods that set up the initial state — their design determines how easy or difficult the class is to use correctly. This entry covers constructors in depth, constructor overloading and chaining, copy constructors, factory methods, the builder pattern, and the difference between shallow and deep copy.