Back to Blog
Java

Java Initialization Order Explained

Understand the exact sequence of static, instance, and constructor initialization in Java, including how inheritance affects the order and common pitfalls to avoid.

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 initialized, not merely when its bytecode is 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 initialized, the output is first, second, third. The static initializer block is placed between the field declarations, and the runtime respects that source order. Directly reading a later-declared static field by its simple name from a static initializer is a compile-time error; an indirect read through a method can still observe the default value (zero, null, false) before the field initializer has run.

Instance Initialization Order

Instance initialization happens when a new object is created. The sequence is more involved than simply running the constructor. The compiler arranges instance field initializers and instance initializer blocks after the superclass constructor call and before the constructor body. 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. This assignment is legal because the block runs after default values are set, but it can be confusing. A direct read of a later-declared field by its simple name in an initializer block is an illegal forward reference; assigning to such a field in a block is allowed.

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 run after the superclass constructor call and before the constructor body. 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, a direct simple-name read of values would be an illegal forward reference; an indirect read through a method could see null and throw a NullPointerException. This illustrates why order matters.

Common Pitfalls with Initialization Order

One frequent mistake is reading a static field from a static initializer block before that field is declared. A direct simple-name read is rejected as an illegal forward reference; an indirect read through a method can silently observe the default 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 initialization. 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 initialization. If a static initializer triggers initialization 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 can have static fields and static methods, but they do not have instance initializer blocks or static initializer blocks. Static fields in interfaces are implicitly public static final, and when the interface is initialized, its fields are initialized in source order. However, interface initialization is not triggered merely by a class implementing the interface. It can be triggered by actual use of a static member, such as reading a non-constant static field or invoking a static method. This differs from class inheritance, where the superclass is initialized before the subclass. If a class implements an interface but never touches the interface's static members, the interface may stay uninitialized.

A practical consequence is that you cannot rely on an interface's field initializers running just because a class implements it. If you need guaranteed initialization, use a class instead of an interface. If you must keep the constants in an interface, trigger initialization from the class's own static initializer by referencing a non-constant interface field or invoking a static interface method.

Java Initialization Order: Static Fields, Instance Initializers, and Constructors | RYUSLOG DEV