Back to Blog
Java

Java Initialization Order Explained

java initialization order: Understand the exact sequence of static, instance, and constructor initialization in Java, including inheritance and common pitfalls.

JavaInitializationStatic BlocksConstructorsInheritance
Diagram showing the order of Java initialization: superclass static, subclass static, superclass instance, subclass instance.

Java initialization order is a runtime behavior that determines when fields are set, when static blocks run, and when constructors execute. Misunderstanding this order leads to subtle bugs like null values appearing where defaults were expected, or static state being used before it is ready. This article walks through the exact sequence for both static and instance initialization, with attention to inheritance and common mistakes.

Static Initialization Sequence

Static members belong to the class itself, not to any instance. The Java runtime initializes static fields and executes static initializer blocks when the class is first loaded. The order is deterministic: static fields are initialized in the order they appear in the source code, and static initializer blocks run in the order they appear as well. Consider this example:

public class StaticOrder { static int first = getValue("first"); static int second; static { second = getValue("second"); } static int third = getValue("third"); static int getValue(String label) { System.out.println(label); return 1; } }

When StaticOrder is first referenced, the output is first, second, third. The static initializer block is placed between the field declarations, and the runtime respects that source order. If a static block references a field declared later, that field still has its default value (zero, null, false) at that moment. This is a frequent source of initialization bugs.

Instance Initialization Order

Instance initialization happens when a new object is created. The sequence is more involved than simply running the constructor. The Java compiler merges field initializers and instance initializer blocks into the constructor, but the order is defined by the language specification. For a single class without inheritance, the order is:

  1. All instance fields are set to their default values (0, null, false).
  2. Instance field initializers and instance initializer blocks run in source order.
  3. The constructor body executes.

Here is a minimal example:

public class InstanceOrder { int a = init("a"); { b = init("b"); } int b; int c = init("c"); InstanceOrder() { System.out.println("constructor"); } int init(String label) { System.out.println(label); return 1; } }

Creating new InstanceOrder() prints a, b, c, then constructor. Notice that b is declared after the initializer block that assigns it. That is legal because the block runs after default values are set, but it can be confusing. The compiler does not require forward declaration for instance fields in initializer blocks, but the field must be declared somewhere in the class.

Superclass Initialization Before Subclass

When a class extends another, the superclass must be fully initialized before the subclass begins its own instance initialization. This is true for both static and instance initialization. Static initialization of the superclass happens before static initialization of the subclass, and instance initialization of the superclass happens before instance initialization of the subclass.

Consider this inheritance hierarchy:

class Parent { static { System.out.println("Parent static"); } { System.out.println("Parent instance"); } Parent() { System.out.println("Parent constructor"); } } class Child extends Parent { static { System.out.println("Child static"); } { System.out.println("Child instance"); } Child() { System.out.println("Child constructor"); } }

Creating new Child() produces:

Parent static
Child static
Parent instance
Parent constructor
Child instance
Child constructor

The superclass constructor runs before any subclass instance initializer or constructor body. This is why a subclass constructor cannot rely on instance fields of the subclass being initialized when the superclass constructor is executing. If the superclass constructor calls an overridable method, that method may see the subclass fields in their default state, which is a classic source of bugs.

Field Initialization and Initializer Blocks

Java allows both field initializers and instance initializer blocks. They are equivalent in terms of timing; both are compiled into the constructor after the superclass constructor call. The order is strictly source order. You can mix them, but doing so can make the code harder to follow. A common practice is to use field initializers for simple assignments and initializer blocks for logic that cannot be expressed in a single expression, such as loops or error handling.

Here is an example that uses an instance initializer block to populate an array:

public class ArrayInit { private int[] values = new int[5]; { for (int i = 0; i < values.length; i++) { values[i] = i * i; } } }

The block runs after values is initialized to a new array of zeros, so the loop fills it safely. If the block were placed before the field declaration, values would still be null and the loop would throw a NullPointerException. This illustrates why order matters.

Common Pitfalls with Initialization Order

One frequent mistake is referencing a static field from a static initializer block before that field is declared. The field has its default value, not the intended value. Another pitfall is calling an overridable method from a constructor. Because the subclass instance initializers have not run yet, the method may observe default values or throw an exception.

Consider this example:

class Base { Base() { print(); } void print() { System.out.println("Base"); } } class Derived extends Base { private String name = "Derived"; @Override void print() { System.out.println(name); } }

Creating new Derived() prints null, not Derived. The superclass constructor runs before the subclass field initializer, so name is still null when print() is called. The fix is to avoid calling overridable methods from constructors, or to initialize fields before the superclass constructor runs, which is impossible in Java. The only safe approach is to design constructors to call only private or final methods.

Runtime Cost and Performance Considerations

Initialization order itself has minimal runtime cost; the JVM executes the compiled bytecode in the defined sequence. However, the way you write initializers can affect performance. For instance, a static initializer that performs heavy computation runs once per class load. If that work is not needed until a specific method is called, moving it to a lazy initialization pattern can reduce startup time. Similarly, instance initializer blocks that do unnecessary work on every object creation add overhead.

Another performance aspect is the order of static initialization in relation to class loading. If a static initializer triggers loading of another class, that can cause circular initialization dependencies. The JVM handles these by allowing the class to be used in an incomplete state, which can lead to unexpected null values. Avoiding circular static dependencies is a maintainability and reliability concern more than a raw performance issue.

Maintainability and Readability of Initialization Code

Keeping initialization logic predictable makes the code easier to maintain. Prefer field initializers for simple assignments. Use instance initializer blocks sparingly, and only when the logic cannot be expressed as a simple expression. If you must use a static block, place it after the fields it depends on, and document the dependency. The order of declarations should mirror the order of execution to reduce confusion.

A useful technique is to extract complex initialization logic into a private static method and call it from the field initializer. This keeps the field declaration concise and the logic testable. For example:

public class Config { private static final Map<String, String> DEFAULTS = buildDefaults(); private static Map<String, String> buildDefaults() { Map<String, String> map = new HashMap<>(); map.put("host", "localhost"); map.put("port", "8080"); return map; } }

This approach avoids a large static block and makes the initialization order obvious. It also allows the method to be unit-tested independently if needed.

Initialization in the Presence of Inheritance and Interfaces

Interfaces in Java can have static fields and static methods, but they do not have instance initializers. Static fields in interfaces are implicitly public static final and must be initialized with a constant expression or a static initializer block (allowed since Java 8). The initialization order for an interface is similar to a class: static fields are initialized in source order when the interface is first used. However, interface initialization is triggered only when a static field is accessed or when a static method is invoked, not when a class implements the interface. This subtlety can cause surprising behavior if you expect interface initialization to happen when a class is loaded.

For example, if a class implements an interface but never uses its static fields, the interface is not initialized. This is different from class inheritance, where the superclass is always initialized before the subclass. Understanding this distinction is important when you rely on interface static fields for configuration or constants.

A practical consequence is that you cannot assume that implementing an interface runs its static initializer. If you need guaranteed initialization, use a class instead of an interface, or explicitly reference the interface's static field in the class's own static initializer.

java initialization order: Practical Usage and Code Examples | RYUSLOG DEV